// Types REST partagés (préfixe /api/v1).
import type { CloneOperation, GroupSummary, PostCreateHook, RepoSummary, SessionSummary, SettingsBroadcast, WorktreeSummary } from './protocol.js';

export interface ApiError {
  error: { code: string; message: string; details?: unknown };
}

export interface LoginRequest {
  token: string;
}
export interface LoginResponse {
  ok: true;
  label: string;
}
export interface MeResponse {
  ok: true;
  /** id du token de la session courante : sert à marquer « courant » dans la liste des tokens. */
  tokenId: string;
  tokenLabel: string;
  serverVersion: string;
}

// ---- Gestion des tokens d'accès (onglet Réglages) ----
export interface TokenInfo {
  id: string;
  label: string;
  createdAt: string;
  lastUsedAt: string | null;
  /** true pour le token de la session courante. */
  current: boolean;
}
export interface TokensListResponse {
  tokens: TokenInfo[];
}
export interface CreateTokenRequest {
  label: string;
}
export interface CreateTokenResponse {
  id: string;
  label: string;
  /** valeur en clair : affichée une seule fois, jamais re-récupérable. */
  token: string;
}

export interface CreateSessionRequest {
  cwd: string;
  /** binaire à lancer : défaut "claude" ; "bash" sert aux tests d'acceptation sans quota */
  command?: 'claude' | 'bash';
}

/**
 * POST /api/v1/projects : crée un NOUVEAU dossier de projet `<root>/<name>` (le serveur ne crée
 * jamais de dossier ailleurs) puis y lance une session. Le `git init` est optionnel (case à cocher).
 * Réponse : `CreateProjectResponse`.
 */
export interface CreateProjectRequest {
  /** racine absolue où créer le projet (doit déjà exister). */
  root: string;
  /** nom du dossier de projet : un seul segment, sans séparateur ni `..`. */
  name: string;
  /** true → `git init` dans le nouveau dossier avant la session (défaut false). */
  gitInit?: boolean;
  /** binaire à lancer : défaut "claude" ; "bash" sert aux tests sans quota. */
  command?: 'claude' | 'bash';
}
export interface CreateProjectResponse {
  session: SessionSummary;
  /** chemin absolu du dossier de projet créé. */
  path: string;
  /** true si `git init` a réellement été exécuté. */
  gitInitialized: boolean;
}
export interface SessionsListResponse {
  sessions: SessionSummary[];
}
export interface SessionResponse {
  session: SessionSummary;
}
/** POST /sessions/hide-discovered : masque tout l'historique de sessions externes visible. */
export interface HideDiscoveredResponse {
  /** nombre de sessions effectivement masquées. */
  hidden: number;
}

// POST /sessions/:id/resume et /sessions/:id/fork (P2) : aucun corps, cwd et command
// sont TOUJOURS dérivés du :id (lu sur disque), jamais fournis par le client (cf. spike S1).
export type ResumeSessionRequest = Record<string, never>;
export type ForkSessionRequest = Record<string, never>;

// ---- Repos & worktrees (P3) ----
export interface ReposListResponse {
  repos: RepoSummary[];
}
export interface RepoResponse {
  repo: RepoSummary;
}
export interface CreateRepoRequest {
  path: string;
  label?: string;
  postCreateHooks?: PostCreateHook[];
  preTrust?: boolean;
}
export interface UpdateRepoRequest {
  label?: string;
  postCreateHooks?: PostCreateHook[];
  preTrust?: boolean;
  /** true = masquer le repo (exclu du dashboard, conservé en DB) ; false = ré-afficher. */
  hidden?: boolean;
}

/** Résultat d'un scan de découverte (POST /api/v1/repos/discover). */
export interface DiscoverReposResponse {
  /** dossiers-repos trouvés sur disque. */
  scanned: number;
  /** repos réellement insérés (nouveaux, non déjà enregistrés). */
  added: number;
  durationMs: number;
  /** true si la limite (maxRepos / timeout) a été atteinte avant la fin du scan. */
  truncated: boolean;
}

export interface WorktreesListResponse {
  worktrees: WorktreeSummary[];
}
export interface WorktreeResponse {
  worktree: WorktreeSummary;
}
export interface HookRunResult {
  hookId: string;
  label: string;
  exitCode: number | null;
  output: string;
  durationMs: number;
}
/**
 * Stratégie de résolution de la branche d'un nouveau worktree :
 * - `auto` (défaut) : détecte l'existence de la branche, checkout si elle existe en local, suivi de
 *   `origin/<branch>` si elle n'existe que sur le remote, sinon création (`-b`). Robuste pour les
 *   groupes hétérogènes où la branche peut déjà exister dans certains dépôts mais pas d'autres.
 * - `create` : force la création (`-b`), échoue si la branche existe déjà.
 * - `checkout` : force le checkout d'une branche existante, échoue si elle est absente.
 */
export type WorktreeBranchMode = 'auto' | 'create' | 'checkout';
/** Action réellement effectuée par la résolution `auto` (feedback UI). */
export type WorktreeBranchAction = 'created' | 'reused' | 'tracked';

export interface CreateWorktreeRequest {
  branch: string;
  /** stratégie de résolution de la branche (défaut : `auto`). Voir {@link WorktreeBranchMode}. */
  mode?: WorktreeBranchMode;
  /** @deprecated remplacé par `mode` ; mappé : true→create, false→checkout, absent→auto. */
  newBranch?: boolean;
  /** point de départ d'une branche créée (défaut : branche par défaut du dépôt, sinon HEAD). */
  baseRef?: string;
  /** chemin cible du worktree (défaut : `<parent>/<repo>-wt-<branch>`). */
  path?: string;
  runHooks?: boolean;
  preTrust?: boolean;
  /** lancer une session dans le worktree créé (défaut : aucune). */
  startSession?: 'claude' | 'bash' | null;
}
export interface CreateWorktreeResponse {
  worktree: WorktreeSummary;
  hookResults: HookRunResult[];
  session: SessionSummary | null;
  /** action effective de la résolution de branche (créée / réutilisée / suivie du remote). */
  action: WorktreeBranchAction;
}

/** GET /api/v1/repos/:id/branches : branches locales/remote pour alimenter un sélecteur de base. */
export interface RepoBranchesResponse {
  local: string[];
  remote: string[];
  /** branche par défaut du dépôt (origin/HEAD), ou null si indéterminée. */
  default: string | null;
}

/** POST /api/v1/repos/:id/worktrees/commit : `git add -A` puis commit dans le worktree visé. */
export interface CommitWorktreeRequest {
  path: string;
  message: string;
  /**
   * P7 : `all` (défaut, rétrocompat) : `git add -A` + commit (tout l'arbre) ; `staged` : commit
   * de l'index uniquement (staging sélectif préalable via /stage). `amend` réécrit le dernier
   * commit (refusé s'il est déjà poussé).
   */
  mode?: 'all' | 'staged';
  amend?: boolean;
}

// ---- P7 : diff par fichier, statut détaillé, contenu de fichier (IDE worktree) ----
/** Un fichier modifié dans un worktree (sortie de `git status --porcelain=v2` + `--numstat`). */
export interface FileChange {
  /** chemin relatif à la racine du worktree (POSIX). */
  path: string;
  /** code d'état dans l'index (X de XY porcelain), '.' si inchangé. */
  indexStatus: string;
  /** code d'état dans l'arbre de travail (Y de XY porcelain), '.' si inchangé. */
  worktreeStatus: string;
  /** true si une partie est indexée (staged). */
  staged: boolean;
  /** true si une partie est non indexée (unstaged) ou non suivie (untracked). */
  unstaged: boolean;
  /** true pour les fichiers non suivis (untracked). */
  untracked: boolean;
  /** true pour les fichiers en conflit de merge (XY de type U/AA/DD). */
  conflicted: boolean;
  /** lignes ajoutées (null si binaire). */
  insertions: number | null;
  /** lignes supprimées (null si binaire). */
  deletions: number | null;
  /** true si git considère le fichier comme binaire (numstat = '-'). */
  binary: boolean;
  /** ancien chemin pour un renommage/copie, sinon absent. */
  renamedFrom?: string;
}
/** GET /api/v1/repos/:id/worktrees/changes?path= : liste des fichiers modifiés d'un worktree. */
export interface WorktreeChangesResponse {
  repoId: string;
  path: string;
  changes: FileChange[];
  /** true si la liste a été bornée (trop de changements). */
  truncated: boolean;
}
/** GET /api/v1/repos/:id/worktrees/diff?path=&file=&staged= : diff unifié d'un fichier. */
export interface FileDiffResponse {
  path: string;
  file: string;
  staged: boolean;
  binary: boolean;
  /** true si le diff a été tronqué (fichier trop volumineux). */
  tooLarge: boolean;
  /** texte du diff unifié git (vide si binaire ou tooLarge). */
  diff: string;
}
/** GET /api/v1/repos/:id/files/content?wt=&path= : contenu d'un fichier (pour l'éditeur Monaco). */
export interface FileContentResponse {
  /** chemin relatif au worktree (POSIX). */
  path: string;
  content: string;
  encoding: 'utf-8';
  size: number;
  /** mtime du fichier en ms (st.mtimeMs) : base du garde-fou de conflit d'édition (P8). */
  mtime: number;
  /** langage déduit de l'extension (pour Monaco), si reconnu. */
  language?: string;
}
/** PUT /api/v1/repos/:id/files/content : écriture d'un fichier (borné au worktree). */
export interface WriteFileRequest {
  /** chemin absolu du worktree. */
  wt: string;
  /** chemin relatif au worktree (POSIX). */
  path: string;
  content: string;
  /** mtime (ms) lu au chargement ; si fourni et que le fichier a changé depuis → 409 STALE_FILE (P8). */
  baseMtime?: number;
}
export interface WriteFileResponse {
  ok: true;
  size: number;
  /** mtime (ms) du fichier après écriture : le client le réutilise comme nouvelle base. */
  mtime: number;
}
/** P7 : corps commun des mutations de staging/discard. */
export interface WorktreeFilesRequest {
  path: string;
  /** chemins relatifs au worktree. */
  files: string[];
}
/** DELETE des changements locaux ; `includeUntracked` autorise `git clean` (destructif, opt-in). */
export interface DiscardFilesRequest extends WorktreeFilesRequest {
  includeUntracked?: boolean;
}
/** POST /api/v1/repos/:id/worktrees/fetch : `git fetch --all --prune`. */
export interface FetchWorktreeRequest {
  path: string;
}
/** POST /api/v1/repos/:id/worktrees/pull : `git pull` (ff-only par défaut). */
export interface PullWorktreeRequest {
  path: string;
  mode?: 'ff-only' | 'rebase';
}
/** POST /api/v1/repos/:id/worktrees/push : pousse la branche du worktree (upstream auto si absent). */
export interface PushWorktreeRequest {
  path: string;
}
/**
 * POST /api/v1/repos/:id/worktrees/promote : « passer en principal » : la branche du worktree devient
 * le checkout principal du dépôt (le worktree est supprimé, l'ancienne branche principale est conservée).
 */
export interface PromoteWorktreeRequest {
  path: string;
  /** outrepasse les garde-fous d'arbre sale (worktree/checkout principal). */
  force?: boolean;
}
export interface AdoptWorktreeRequest {
  path: string;
  runHooks?: boolean;
  preTrust?: boolean;
}

/**
 * POST /api/v1/repos/:id/session : lance UNE session dans le checkout principal du repo
 * (`repo.path`) pour « bosser sur la branche principale » sans créer de worktree.
 * Réponse : `SessionResponse`.
 */
export interface StartRepoSessionRequest {
  /** binaire à lancer : défaut "claude" ; "bash" sert aux tests sans quota. */
  command?: 'claude' | 'bash';
  /** si fourni : crée/bascule cette branche dans le checkout principal avant la session. */
  branch?: string;
  /** true (défaut quand `branch` fourni) : `git switch -c` ; false : bascule sur une branche existante. */
  newBranch?: boolean;
}

// ---- Groupes de travail (P5) ----
export interface GroupsListResponse {
  groups: GroupSummary[];
}
export interface GroupResponse {
  group: GroupSummary;
}
export interface CreateGroupRequest {
  label: string;
  description?: string;
  color?: string;
  /** ids de repos initiaux (défaut : []). */
  repoIds?: string[];
}
export interface UpdateGroupRequest {
  label?: string;
  /** null pour effacer. */
  description?: string | null;
  color?: string | null;
}
export interface AddRepoRequest {
  repoId: string;
}

// ---- Session de groupe multi-repo (P6) ----
/** Lance UNE session Claude couvrant tous les repos du groupe (via `--add-dir`). */
export interface CreateGroupSessionRequest {
  /** défaut : claude. */
  command?: 'claude' | 'bash';
  /** présent → couvre le worktree de cette branche dans chaque repo ; absent → les checkouts principaux. */
  branch?: string;
}
export interface GroupSessionResponse {
  session: SessionSummary;
  /** répertoires effectivement couverts par la session (cwd primaire en tête). */
  dirs: string[];
  /** repos du groupe pour lesquels aucun worktree n'a pu être résolu. */
  skipped: Array<{ repoId: string; reason: string }>;
}

// ---- Navigateur de répertoires (sélecteur de dossier côté web) ----
export interface FsEntry {
  name: string;
  /** chemin absolu de l'entrée */
  path: string;
  /** présent (true) uniquement en mode markRepos quand le dossier est un dépôt git */
  isRepo?: boolean;
  /** P7 : présent (true) en mode includeFiles quand l'entrée est un fichier (pas un dossier) */
  isFile?: boolean;
}
export interface FsListResponse {
  /** chemin absolu listé (normalisé) */
  path: string;
  /** parent (null à la racine `/`) : pour le bouton « remonter » */
  parent: string | null;
  /** home de l'utilisateur côté serveur : point de départ par défaut */
  home: string;
  /** sous-dossiers uniquement, triés sans tenir compte de la casse */
  entries: FsEntry[];
}

// ---- Web Push (P4) ----
export interface VapidKeyResponse {
  /** clé publique VAPID (applicationServerKey côté navigateur). */
  key: string;
}
export interface PushSubscribeRequest {
  endpoint: string;
  keys: { p256dh: string; auth: string };
}
export interface PushUnsubscribeRequest {
  endpoint: string;
}

// ---- Réglages & info serveur (onglet Réglages) ----
/** Config runtime non sensible du daemon : lecture seule (changée via flags CLI + redémarrage). */
export interface ServerInfo {
  version: string;
  port: number;
  bind: string;
  allowedOrigins: string[];
  dataDir: string;
  /** clé publique VAPID (sûre à exposer) ; null si push indisponible. */
  vapidPublicKey: string | null;
  vapidContact: string;
  /** racine effective de l'install Claude (~/.claude, --claude-home, ou réglage claude_home). */
  claudeHome: string;
  /** diagnostic de résolution du binaire `claude` (présence + exécutabilité). */
  claudeBin: ClaudeBinDiagnostic;
}
/** Diagnostic de résolution du CLI `claude` exposé en lecture dans les Réglages. */
export interface ClaudeBinDiagnostic {
  /** chemin résolu du binaire, ou null si introuvable. */
  path: string | null;
  /** 'configured' = réglage explicite ; 'path' = trouvé via PATH ; null = introuvable. */
  source: 'configured' | 'path' | null;
  /** true si le binaire est présent et exécutable. */
  ok: boolean;
}
export interface SettingsResponse {
  /** réglages modifiables à chaud (allow-list serveur, jamais les secrets). Cf. SettingsBroadcast. */
  settings: SettingsBroadcast;
  server: ServerInfo;
}
export interface UpdateSettingsRequest {
  /** racines absolues à scanner (chemins normalisés, ≤ 16) ; remplace la liste. */
  scanRoots?: string[];
  /** intervalle du re-scan périodique en minutes (0-1440 ; 0 désactive). */
  scanIntervalMin?: number;
  /** chemin absolu du binaire `claude` (fichier exécutable) ; '' pour réinitialiser à l'auto-détection. */
  claudeBinPath?: string;
  /** override de la racine ~/.claude (répertoire existant) ; '' pour réinitialiser au défaut. */
  claudeHome?: string;
  /** auto-archivage des sessions terminées après N jours (0 = jamais ; sinon 1-3650). */
  retentionDays?: number;
  /** purge définitive après N jours (0 = désactivée ; sinon 1-3650). */
  purgeDays?: number;
}

// ---- Journal d'audit (conformité entreprise) ----
export interface AuditLogEntry {
  id: string;
  /** horodatage ISO 8601 */
  ts: string;
  /** tokenId de l'acteur, 'system' (auto) ou 'anonymous' (avant auth) */
  actor: string;
  /** verbe.objet, ex. 'token.create', 'settings.update' */
  action: string;
  resourceId: string | null;
  /** métadonnées non sensibles (jamais de secret) */
  details: unknown;
  result: string;
}
export interface AuditLogsResponse {
  entries: AuditLogEntry[];
  /** curseur de pagination (ts à passer en `before`) ; null si fin de liste. */
  nextBefore: string | null;
}

// ---- RGPD : export / suppression des données liées au token authentifié ----
export interface DataExportResponse {
  exportedAt: string;
  tokens: Array<{ id: string; label: string; createdAt: string; lastUsedAt: string | null; current: boolean }>;
  pushSubscriptions: Array<{ endpoint: string; userAgent: string | null; createdAt: string; lastOkAt: string | null }>;
  sessions: Array<{ id: string; cwd: string; command: string; title: string | null; createdAt: string; endedAt: string | null; exitCode: number | null }>;
  settings: { scanRoots: string[]; scanIntervalMin: number };
}
export interface DeleteMyDataRequest {
  /** code de confirmation renvoyé par un premier appel sans `confirm` ; doit être renvoyé pour exécuter. */
  confirm?: string;
}
export interface DeleteMyDataResponse {
  /** 'pending' = confirmation requise (renvoie `confirm`) ; 'done' = suppression effectuée. */
  status: 'pending' | 'done';
  confirm?: string;
  /** récapitulatif de ce qui sera/a été supprimé. */
  summary: { pushSubscriptions: number; tokenRevoked: boolean };
}

// ---- Services git distants + clone (P12) ----
export type GitService = 'gitea' | 'gitlab' | 'github';
export type GitAuthType = 'pat' | 'app_password' | 'ssh_key' | 'oauth';

/**
 * Résumé d'un credential exposé à l'UI. Ne contient JAMAIS le secret : `hasSecret` indique sa
 * présence, `secretLast4` n'en révèle que les 4 derniers caractères (repère visuel, non sensible).
 */
export interface GitCredentialSummary {
  id: string;
  label: string;
  service: GitService;
  baseUrl: string | null;
  authType: GitAuthType;
  username: string | null;
  hasSecret: boolean;
  secretLast4: string | null;
  createdAt: string;
  lastTestedAt: string | null;
  /** diagnostic du dernier /test : 'ok' | 'AUTH_FAILED' | 'RATE_LIMITED' | 'UNREACHABLE' | … */
  testResult: string | null;
}
export interface GitCredentialsListResponse {
  credentials: GitCredentialSummary[];
}
export interface CreateGitCredentialRequest {
  label: string;
  service: GitService;
  /** P12a : 'pat' | 'app_password' (HTTPS). 'ssh_key'/'oauth' arrivent en P12b/P12c. */
  authType: GitAuthType;
  /** instance self-hosted (Gitea/GitLab) ; absent = service public (github.com / gitlab.com). */
  baseUrl?: string;
  username?: string;
  /** PAT ou app password : chiffré côté serveur (SecretBox), jamais relu. */
  secret?: string;
}
export interface UpdateGitCredentialRequest {
  label?: string;
  baseUrl?: string;
  username?: string;
  /** si fourni, re-chiffre le secret ; absent = inchangé. */
  secret?: string;
}
export interface GitCredentialResponse {
  credential: GitCredentialSummary;
}
export interface TestCredentialResponse {
  ok: boolean;
  /** login renvoyé par le service en cas de succès. */
  user?: string;
  /** code d'erreur typé en cas d'échec (AUTH_FAILED, RATE_LIMITED, UNREACHABLE). */
  error?: string;
}

/** Dépôt distant listé via l'API du service. */
export interface RemoteRepoSummary {
  /** owner/name */
  fullName: string;
  /** URL HTTPS de clone. */
  cloneUrl: string;
  private: boolean;
  description: string | null;
  defaultBranch: string | null;
}
export interface RemoteReposResponse {
  repos: RemoteRepoSummary[];
  /** numéro de page suivant si d'autres résultats existent, sinon null. */
  nextPage: number | null;
}

export interface CloneRequest {
  credentialId: string;
  /** URL HTTPS du dépôt à cloner. */
  remoteUrl: string;
  /** dossier de destination (sous une racine de scan) : créé par le clone, refus si existant. */
  dest: string;
}
export interface CloneStartResponse {
  operationId: string;
}
/** État d'un clone (GET /repos/clone/:id) : même forme que le push WS clone_update. */
export interface CloneStatusResponse {
  operation: CloneOperation;
}
