import { writable } from "svelte/store";
import { invoke } from "@tauri-apps/api/core";
import type { PrdTask, PrdFile } from "./prd";

export type TaskStatus = "pending" | "running" | "completed" | "failed" | "blocked";
export type LoopStatus = "idle" | "running" | "paused" | "stopped";

export interface TaskLoopTask extends PrdTask {
  status: TaskStatus;
  conversationId?: string;
}

/** Returns the index of the first pending task, or -1 if none. */
export function findNextPendingIndex(tasks: TaskLoopTask[]): number {
  return tasks.findIndex((t) => t.status === "pending");
}

/** Counts tasks with the given status. */
export function countByStatus(tasks: TaskLoopTask[], status: TaskStatus): number {
  return tasks.filter((t) => t.status === status).length;
}

/**
 * Returns true if a task is blocked — i.e. any of its `dependsOn` IDs refer to a
 * task that has failed or is already blocked.
 */
export function isTaskBlocked(task: TaskLoopTask, allTasks: TaskLoopTask[]): boolean {
  if (!task.dependsOn || task.dependsOn.length === 0) return false;
  return task.dependsOn.some((depId) => {
    const dep = allTasks.find((t) => t.id === depId);
    return dep !== undefined && (dep.status === "failed" || dep.status === "blocked");
  });
}

/**
 * Returns indices of tasks that are ready to start: status is `pending` and all
 * `dependsOn` tasks are `completed`. Respects `limit` (concurrency cap).
 */
export function getReadyTasks(tasks: TaskLoopTask[], limit: number): number[] {
  const ready: number[] = [];
  for (let i = 0; i < tasks.length; i++) {
    if (ready.length >= limit) break;
    const task = tasks[i];
    if (task.status !== "pending") continue;
    const depsAllDone =
      !task.dependsOn ||
      task.dependsOn.length === 0 ||
      task.dependsOn.every((depId) => {
        const dep = tasks.find((t) => t.id === depId);
        return dep === undefined || dep.status === "completed";
      });
    if (depsAllDone) {
      ready.push(i);
    }
  }
  return ready;
}

/**
 * Groups task indices into waves for UI display. Tasks with no pending dependencies
 * form wave 0; tasks whose deps are all in earlier waves form the next wave, etc.
 * Circular dependencies are collected into a final "overflow" wave.
 */
export function computeWaves(tasks: TaskLoopTask[]): number[][] {
  const waves: number[][] = [];
  const assigned = new Set<number>();

  // Build an id→index map
  const idToIndex = new Map<string, number>();
  tasks.forEach((t, i) => idToIndex.set(t.id, i));

  let remaining = tasks.map((_, i) => i).filter((i) => !assigned.has(i));

  while (remaining.length > 0) {
    const wave: number[] = [];
    for (const i of remaining) {
      const task = tasks[i];
      const depsAllAssigned =
        !task.dependsOn ||
        task.dependsOn.length === 0 ||
        task.dependsOn.every((depId) => {
          const depIdx = idToIndex.get(depId);
          return depIdx === undefined || assigned.has(depIdx);
        });
      if (depsAllAssigned) {
        wave.push(i);
      }
    }

    if (wave.length === 0) {
      // Circular dependency — dump all remaining into a single wave
      waves.push(remaining);
      break;
    }

    wave.forEach((i) => assigned.add(i));
    waves.push(wave);
    remaining = remaining.filter((i) => !assigned.has(i));
  }

  return waves;
}

/**
 * Normalises a file-picker path to a Unix path.
 *
 * On Windows/WSL the dialog returns a UNC path like:
 *   \\wsl.localhost\Ubuntu\home\naomi\code\temp\hikari-tasks.json
 * which the WSL-side Claude process cannot use as a working directory.
 * This converts that to /home/naomi/code/temp/hikari-tasks.json.
 */
export function normalizeToUnixPath(path: string): string {
  // Matches both \\wsl.localhost\<distro>\... and \\wsl$\<distro>\... (legacy)
  const wslUncMatch = /^[/\\][/\\]wsl(?:\.localhost|\$)?[/\\][^/\\]+(.*)$/i.exec(path);
  if (wslUncMatch) {
    return wslUncMatch[1].replaceAll("\\", "/");
  }
  return path;
}

/**
 * Builds the prompt sent to Claude after a task completes to commit the changes.
 * If `includeSummary` is true, Claude is also asked to write/append to SUMMARY.md first.
 */
export function buildAutoCommitPrompt(
  task: TaskLoopTask,
  prefix: string,
  includeSummary: boolean,
  sessionTimestamp: string
): string {
  const escapedTitle = task.title.replaceAll('"', '\\"');
  const commitMsg = `${prefix}: ${escapedTitle}\\n\\nAuto-committed by Hikari Task Loop\\nTask ID: ${task.id}\\nLoop session: ${sessionTimestamp}`;

  const gitCommands = `git add -A && git commit -m "${commitMsg}"`;

  const summaryRequest = includeSummary
    ? `\n\nBefore committing, please write or append to \`SUMMARY.md\` in the working directory with:\n- What was implemented\n- Key decisions made\n- Files changed\n- Any caveats or follow-up work\n\nInclude SUMMARY.md in the commit.\n`
    : "";

  return `[Auto-commit] Please run the following in the current working directory:${summaryRequest}

\`\`\`bash
${gitCommands}
\`\`\`

If this fails (e.g. nothing to commit, no git repository), acknowledge it briefly and do not retry.`;
}

/** Builds the prompt sent to Claude Code for an automated task. */
export function buildTaskPrompt(
  task: TaskLoopTask,
  taskNumber: number,
  totalTasks: number
): string {
  return `[Automated Task Loop — Task ${taskNumber}/${totalTasks}]\n\n**${task.title}**\n\n${task.prompt}`;
}

function createTaskLoopStore() {
  const tasks = writable<TaskLoopTask[]>([]);
  const loopStatus = writable<LoopStatus>("idle");
  const currentTaskIndex = writable<number>(-1);
  const sourceFile = writable<string>("");
  const concurrencyLimit = writable<number>(3);

  async function loadFile(path: string): Promise<void> {
    const content = await invoke<string>("read_file_content", { path });
    const data = JSON.parse(content) as PrdFile;
    const loopTasks: TaskLoopTask[] = data.tasks.map((t) => ({ ...t, status: "pending" }));
    tasks.set(loopTasks);
    sourceFile.set(path);
    loopStatus.set("idle");
    currentTaskIndex.set(-1);
  }

  function setTaskStatus(index: number, status: TaskStatus): void {
    tasks.update((current) => {
      const result = [...current];
      if (result[index]) {
        result[index] = { ...result[index], status };
      }
      return result;
    });
  }

  function setTaskConversationId(index: number, conversationId: string): void {
    tasks.update((current) => {
      const result = [...current];
      if (result[index]) {
        result[index] = { ...result[index], conversationId };
      }
      return result;
    });
  }

  function setLoopStatus(status: LoopStatus): void {
    loopStatus.set(status);
  }

  function setCurrentTaskIndex(index: number): void {
    currentTaskIndex.set(index);
  }

  function setConcurrencyLimit(limit: number): void {
    concurrencyLimit.set(Math.max(1, limit));
  }

  function reset(): void {
    tasks.set([]);
    loopStatus.set("idle");
    currentTaskIndex.set(-1);
    sourceFile.set("");
  }

  return {
    tasks: { subscribe: tasks.subscribe },
    loopStatus: { subscribe: loopStatus.subscribe },
    currentTaskIndex: { subscribe: currentTaskIndex.subscribe },
    sourceFile: { subscribe: sourceFile.subscribe },
    concurrencyLimit: { subscribe: concurrencyLimit.subscribe },
    loadFile,
    setTaskStatus,
    setTaskConversationId,
    setLoopStatus,
    setCurrentTaskIndex,
    setConcurrencyLimit,
    reset,
  };
}

export const taskLoopStore = createTaskLoopStore();
