// Enregistrement de toutes les commandes de l'extension. Les handlers contextuels reçoivent le nœud
// d'arbre cliqué (ou via menu) et narrowent dessus ; les mutations passent par le RestClient et l'état
// se met à jour via les events WS (pas de refetch manuel, sauf hide qui sort la session de la liste).
import * as vscode from 'vscode';
import type { GroupSummary, RepoSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared';
import { RestError, type RestClient } from './api/rest-client.js';
import { workspaceUrl } from './config.js';
import type { ArbWsClient, AttachmentSink } from './api/ws-client.js';
import type { Store } from './state/store.js';
import type { SessionTerminalManager } from './terminal/session-pty.js';
import type { WorkspaceMapper } from './workspace.js';
import type { AnyNode } from './views/nodes.js';

export interface CommandDeps {
  store: Store;
  rest: RestClient;
  ws: ArbWsClient;
  terminals: SessionTerminalManager;
  workspace: WorkspaceMapper;
  signIn(): Promise<void>;
  signOut(): Promise<void>;
  reseed(): Promise<void>;
}

// ---- extraction des nœuds ----
function asSession(node: AnyNode | undefined): SessionSummary | undefined {
  return node && (node.kind === 'session' || node.kind === 'group-session') ? node.session : undefined;
}
function asWorktree(node: AnyNode | undefined): { repoId: string; worktree: WorktreeSummary } | undefined {
  return node?.kind === 'worktree' ? { repoId: node.repoId, worktree: node.worktree } : undefined;
}
function asRepo(node: AnyNode | undefined): RepoSummary | undefined {
  return node?.kind === 'repo' ? node.repo : undefined;
}
function asGroup(node: AnyNode | undefined): GroupSummary | undefined {
  return node?.kind === 'group' ? node.group : undefined;
}

/** Exécute une action en affichant proprement une erreur REST (code + message serveur). */
async function run(label: string, fn: () => Promise<void>): Promise<void> {
  try {
    await fn();
  } catch (err) {
    const msg = err instanceof RestError ? `${err.code}: ${err.message}` : (err as Error).message;
    void vscode.window.showErrorMessage(`Arboretum · ${label}: ${msg}`);
  }
}

/** Répond à un dialogue : via le terminal ouvert si présent, sinon par une attache éphémère. */
export async function answerSession(
  deps: CommandDeps,
  session: SessionSummary,
  action: 'select' | 'confirm' | 'deny',
  optionN?: number,
): Promise<void> {
  const open = deps.terminals.getBridge(session.id);
  if (open) {
    open.answer(action, optionN);
    return;
  }
  const noop: AttachmentSink = { data: () => {}, reset: () => {}, detached: () => {}, controlChanged: () => {} };
  const att = await deps.ws.attach({ sessionId: session.id, mode: 'interactive', cols: 80, rows: 24, sink: noop });
  att.answer(action, optionN);
  setTimeout(() => att.detach(), 600); // laisse le serveur traiter avant de fermer le canal éphémère
}

export function registerCommands(context: vscode.ExtensionContext, deps: CommandDeps): void {
  const { store, rest, terminals, workspace } = deps;

  const cmd = (id: string, handler: (...args: unknown[]) => unknown): void => {
    context.subscriptions.push(vscode.commands.registerCommand(id, handler));
  };

  // ---- auth / global ----
  cmd('arboretum.signIn', () => deps.signIn());
  cmd('arboretum.signOut', () => deps.signOut());
  cmd('arboretum.refresh', () => run('refresh', () => deps.reseed()));
  cmd('arboretum.openDashboard', () => {
    void vscode.env.openExternal(vscode.Uri.parse(rest.url));
  });

  // ---- terminaux (Phase B) ----
  cmd('arboretum.attachSession', (node) => {
    const s = asSession(node as AnyNode);
    if (s) terminals.open(s, 'interactive');
  });
  cmd('arboretum.observeSession', (node) => {
    const s = asSession(node as AnyNode);
    if (s) terminals.open(s, 'observer');
  });
  cmd('arboretum.showWaiting', async () => {
    const waiting = store.waitingSessions();
    if (waiting.length === 0) {
      void vscode.window.showInformationMessage('Arboretum: no session waiting.');
      return;
    }
    const pick = await vscode.window.showQuickPick(
      waiting.map((s) => ({ label: s.title?.trim() || s.command, description: s.waitingFor ?? s.cwd, session: s })),
      { placeHolder: 'Sessions waiting for input' },
    );
    if (pick) terminals.open(pick.session, 'interactive');
  });

  // ---- answer (Phase C) ----
  cmd('arboretum.answerSession', async (node) => {
    const s = asSession(node as AnyNode);
    if (!s) return;
    const dialogOptions = s.dialog?.options ?? [];
    if (dialogOptions.length > 0) {
      const pick = await vscode.window.showQuickPick(
        dialogOptions.map((o) => ({ label: o.label, option: o })),
        { placeHolder: s.dialog?.waitingFor ?? 'Choose an option' },
      );
      if (pick) await run('answer', () => answerSession(deps, s, 'select', pick.option.n));
      return;
    }
    const pick = await vscode.window.showQuickPick(['Yes', 'No'], { placeHolder: s.waitingFor ?? 'Answer the prompt' });
    if (pick) await run('answer', () => answerSession(deps, s, pick === 'Yes' ? 'confirm' : 'deny'));
  });

  // ---- sessions ----
  cmd('arboretum.killSession', async (node) => {
    const s = asSession(node as AnyNode);
    if (!s) return;
    const ok = await vscode.window.showWarningMessage(`Kill session "${s.title?.trim() || s.command}"?`, { modal: true }, 'Kill');
    if (ok === 'Kill') await run('kill', async () => void (await rest.killSession(s.id)));
  });
  cmd('arboretum.hideSession', (node) => {
    const s = asSession(node as AnyNode);
    if (!s) return;
    void run('hide', async () => {
      await rest.hideSession(s.id);
      await deps.reseed(); // la session masquée sort de la liste (listSessions sans includeHidden)
    });
  });
  cmd('arboretum.resumeSession', (node) => {
    const s = asSession(node as AnyNode);
    if (!s) return;
    void run('resume', async () => {
      const res = await rest.resumeSession(s.id);
      terminals.open(res.session, 'interactive');
    });
  });
  cmd('arboretum.forkSession', (node) => {
    const s = asSession(node as AnyNode);
    if (!s) return;
    void run('fork', async () => {
      const res = await rest.forkSession(s.id);
      terminals.open(res.session, 'interactive');
    });
  });

  // ---- worktrees (Phase C) ----
  cmd('arboretum.createWorktree', (node) => {
    const repo = asRepo(node as AnyNode);
    if (repo) void createWorktreeFlow(deps, repo.id);
  });
  cmd('arboretum.commitWorktree', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (!wt) return;
    void run('commit', async () => {
      const message = await vscode.window.showInputBox({ prompt: 'Commit message', placeHolder: 'Describe your changes' });
      if (!message) return;
      await rest.commitWorktree(wt.repoId, { path: wt.worktree.path, message });
      void vscode.window.showInformationMessage('Arboretum: committed.');
    });
  });
  cmd('arboretum.pushWorktree', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (!wt) return;
    void vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Arboretum: pushing…' }, () =>
      run('push', async () => {
        await rest.pushWorktree(wt.repoId, { path: wt.worktree.path });
        void vscode.window.showInformationMessage('Arboretum: pushed.');
      }),
    );
  });
  cmd('arboretum.fetchWorktree', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (!wt) return;
    void vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Arboretum: fetching…' }, () =>
      run('fetch', async () => {
        await rest.fetchWorktree(wt.repoId, { path: wt.worktree.path });
        void vscode.window.showInformationMessage('Arboretum: fetched.');
      }),
    );
  });
  cmd('arboretum.pullWorktree', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (!wt) return;
    void run('pull', async () => {
      const mode = await vscode.window.showQuickPick(
        [
          { label: 'Fast-forward only', mode: 'ff-only' as const },
          { label: 'Rebase', mode: 'rebase' as const },
        ],
        { placeHolder: 'Pull strategy' },
      );
      if (!mode) return;
      await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Arboretum: pulling…' }, async () => {
        await rest.pullWorktree(wt.repoId, { path: wt.worktree.path, mode: mode.mode });
        void vscode.window.showInformationMessage('Arboretum: pulled.');
      });
    });
  });
  cmd('arboretum.promoteWorktree', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (wt) void promoteWorktreeFlow(deps, wt.repoId, wt.worktree);
  });
  cmd('arboretum.openWorktreeIde', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (!wt) return;
    void vscode.env.openExternal(vscode.Uri.parse(workspaceUrl(rest.url, wt.repoId, wt.worktree.path)));
  });

  // ---- démarrage de session ----
  cmd('arboretum.startSession', (node) => {
    const wt = asWorktree(node as AnyNode);
    if (wt) {
      void run('start session', async () => {
        const res = await rest.createSession({ cwd: wt.worktree.path, command: 'claude' });
        terminals.open(res.session, 'interactive');
      });
      return;
    }
    const repo = asRepo(node as AnyNode);
    if (repo) {
      void run('start session', async () => {
        const res = await rest.startRepoSession(repo.id, { command: 'claude' });
        terminals.open(res.session, 'interactive');
      });
    }
  });
  cmd('arboretum.startGroupSession', (node) => {
    const group = asGroup(node as AnyNode);
    if (group) void startGroupSessionFlow(deps, group);
  });

  // ---- conscience du workspace (Phase D) ----
  cmd('arboretum.startSessionHere', () => {
    const repoId = workspace.currentRepoId();
    if (!repoId) {
      void vscode.window.showWarningMessage('Arboretum: the current folder is not a known repo/worktree.');
      return;
    }
    const folder = vscode.workspace.workspaceFolders?.[0];
    if (!folder) return;
    void run('start session', async () => {
      const res = await rest.createSession({ cwd: folder.uri.fsPath, command: 'claude' });
      terminals.open(res.session, 'interactive');
    });
  });
  cmd('arboretum.createWorktreeHere', () => {
    const repoId = workspace.currentRepoId();
    if (!repoId) {
      void vscode.window.showWarningMessage('Arboretum: the current folder is not a known repo.');
      return;
    }
    void createWorktreeFlow(deps, repoId);
  });
  cmd('arboretum.revealWorktree', () => workspace.revealCurrent());
}

// ---- flows interactifs ----

async function createWorktreeFlow(deps: CommandDeps, repoId: string): Promise<void> {
  await run('create worktree', async () => {
    const branches = await deps.rest.getBranches(repoId);
    const existing = [...new Set([...branches.local, ...branches.remote.map((b) => b.replace(/^origin\//, ''))])].sort();
    const NEW = '$(add) New branch…';
    const items = [NEW, ...existing];
    const choice = await vscode.window.showQuickPick(items, { placeHolder: 'Branch for the new worktree (auto: checkout if exists, else create)' });
    if (!choice) return;
    let branch = choice;
    if (choice === NEW) {
      const input = await vscode.window.showInputBox({ prompt: 'New branch name' });
      if (!input) return;
      branch = input.trim();
    }
    if (!branch) return;
    const res = await deps.rest.createWorktree(repoId, { branch, mode: 'auto' });
    void vscode.window.showInformationMessage(`Arboretum: worktree ${res.action} for "${branch}".`);
  });
}

async function promoteWorktreeFlow(deps: CommandDeps, repoId: string, worktree: WorktreeSummary): Promise<void> {
  const label = worktree.branch ?? worktree.path;
  const confirm = await vscode.window.showWarningMessage(
    `Promote "${label}" to the repo's main checkout? The worktree is removed (branch kept).`,
    { modal: true },
    'Promote',
  );
  if (confirm !== 'Promote') return;
  try {
    await deps.rest.promoteWorktree(repoId, { path: worktree.path });
    void vscode.window.showInformationMessage(`Arboretum: "${label}" promoted to main.`);
  } catch (err) {
    if (err instanceof RestError && err.status === 409) {
      const force = await vscode.window.showWarningMessage(
        `Arboretum: working tree is dirty (${err.message}). Promote anyway?`,
        { modal: true },
        'Force promote',
      );
      if (force === 'Force promote') {
        await run('promote', async () => {
          await deps.rest.promoteWorktree(repoId, { path: worktree.path, force: true });
          void vscode.window.showInformationMessage(`Arboretum: "${label}" promoted (forced).`);
        });
      }
      return;
    }
    const msg = err instanceof RestError ? `${err.code}: ${err.message}` : (err as Error).message;
    void vscode.window.showErrorMessage(`Arboretum · promote: ${msg}`);
  }
}

async function startGroupSessionFlow(deps: CommandDeps, group: GroupSummary): Promise<void> {
  await run('start group session', async () => {
    const branch = await vscode.window.showInputBox({
      prompt: `Branch to cover in each repo of "${group.label}" (leave empty for the main checkouts)`,
      placeHolder: 'feature/cross-repo (optional)',
    });
    if (branch === undefined) return; // annulé (chaîne vide = checkouts principaux)
    const body = branch.trim() ? { command: 'claude' as const, branch: branch.trim() } : { command: 'claude' as const };
    const res = await deps.rest.createGroupSession(group.id, body);
    deps.terminals.open(res.session, 'interactive');
    if (res.skipped.length > 0) {
      void vscode.window.showWarningMessage(
        `Arboretum: ${res.skipped.length} repo(s) skipped: ${res.skipped.map((s) => s.reason).join('; ')}`,
      );
    }
  });
}
