/**
 * Zustand store for Versions & Snapshots panel.
 *
 * Manages transaction lifecycle (list, create, commit, rollback)
 * and per-transaction snapshot entries.
 */

import { createStore as create } from "./create-store.js";
import type { FetchClient } from "@nexus-ai-fs/api-client";
import { createApiAction, categorizeError } from "./create-api-action.js";
import { useErrorStore } from "./error-store.js";
import { useUiStore } from "./ui-store.js";

// =============================================================================
// Types (snake_case matching API wire format)
// =============================================================================

export interface Transaction {
  readonly transaction_id: string;
  readonly zone_id: string;
  readonly agent_id: string | null;
  readonly status: "active" | "committed" | "rolled_back" | "expired";
  readonly description: string | null;
  readonly created_at: string;
  readonly expires_at: string;
  readonly entry_count: number;
}

export interface SnapshotEntry {
  readonly entry_id: string;
  readonly transaction_id: string;
  readonly path: string;
  readonly operation: "write" | "delete" | "rename";
  readonly original_hash: string | null;
  readonly new_hash: string | null;
  readonly created_at: string;
}

interface TransactionListResponse {
  readonly transactions: readonly Transaction[];
  readonly count: number;
}

export interface ConflictItem {
  readonly path: string;
  readonly reason: string;
  readonly expected_hash: string | null;
  readonly current_hash: string | null;
  readonly transaction_id: string | null;
}

interface ConflictsResponse {
  readonly conflicts: readonly ConflictItem[];
}

// =============================================================================
// Status filter cycle
// =============================================================================

const STATUS_CYCLE: readonly (string | null)[] = [
  null,
  "active",
  "committed",
  "rolled_back",
  "expired",
];

export function nextStatusFilter(current: string | null): string | null {
  const index = STATUS_CYCLE.indexOf(current);
  const next = (index + 1) % STATUS_CYCLE.length;
  return STATUS_CYCLE[next] ?? null;
}

// =============================================================================
// Store
// =============================================================================

export interface DiffContent {
  readonly old: string;
  readonly new: string;
}

export interface VersionsState {
  // Transaction list
  readonly transactions: readonly Transaction[];
  readonly selectedTransaction: Transaction | null;
  readonly selectedIndex: number;
  readonly statusFilter: string | null;
  readonly isLoading: boolean;
  readonly error: string | null;

  // Entries for selected transaction
  readonly entries: readonly SnapshotEntry[];
  readonly entriesLoading: boolean;

  // Diff viewer
  readonly diffContent: DiffContent | null;
  readonly diffLoading: boolean;
  readonly _diffRequestKey: string;

  // Transaction detail
  readonly transactionDetail: Transaction | null;
  readonly transactionDetailLoading: boolean;

  // Conflicts
  readonly conflicts: readonly ConflictItem[];
  readonly conflictsLoading: boolean;
  readonly showConflicts: boolean;

  // Actions
  readonly fetchTransactions: (client: FetchClient) => Promise<void>;
  readonly selectTransaction: (txn: Transaction) => void;
  readonly setSelectedIndex: (index: number) => void;
  readonly setStatusFilter: (status: string | null) => void;
  readonly fetchEntries: (txnId: string, client: FetchClient) => Promise<void>;
  readonly fetchDiff: (
    path: string,
    version1: string | null,
    version2: string | null,
    transactionId: string,
    client: FetchClient,
  ) => Promise<void>;
  readonly fetchTransactionDetail: (txnId: string, client: FetchClient) => Promise<void>;
  readonly beginTransaction: (
    client: FetchClient,
    description?: string,
    ttlSeconds?: number,
  ) => Promise<void>;
  readonly commitTransaction: (txnId: string, client: FetchClient) => Promise<void>;
  readonly rollbackTransaction: (txnId: string, client: FetchClient) => Promise<void>;
  readonly fetchConflicts: (client: FetchClient) => Promise<void>;
  readonly toggleConflicts: () => void;
  readonly clearDiff: () => void;
}

const SOURCE = "versions";

export const useVersionsStore = create<VersionsState>((set, get) => ({
  transactions: [],
  selectedTransaction: null,
  selectedIndex: 0,
  statusFilter: null,
  isLoading: false,
  error: null,
  entries: [],
  entriesLoading: false,
  diffContent: null,
  diffLoading: false,
  _diffRequestKey: "",
  transactionDetail: null,
  transactionDetailLoading: false,
  conflicts: [],
  conflictsLoading: false,
  showConflicts: false,

  // =========================================================================
  // Actions migrated to createApiAction
  // =========================================================================

  fetchTransactionDetail: createApiAction<VersionsState, [string, FetchClient]>(set, {
    loadingKey: "transactionDetailLoading",
    source: SOURCE,
    errorMessage: "Failed to fetch transaction detail",
    action: async (txnId, client) => {
      const detail = await client.get<Transaction>(
        `/api/v2/snapshots/${encodeURIComponent(txnId)}`,
      );
      return { transactionDetail: detail };
    },
  }),

  // =========================================================================
  // Actions with special patterns — inline with error store integration
  // =========================================================================

  fetchTransactions: async (client) => {
    set({ isLoading: true, error: null });

    try {
      const { statusFilter } = get();
      const query = statusFilter ? `?status=${encodeURIComponent(statusFilter)}` : "";
      const response = await client.get<TransactionListResponse>(
        `/api/v2/snapshots${query}`,
      );

      const transactions = response.transactions ?? [];
      set({ transactions, isLoading: false });
      useUiStore.getState().markDataUpdated("versions");
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to fetch transactions");
      set({
        isLoading: false,
        error: message,
      });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  selectTransaction: (txn) => {
    const { transactions } = get();
    const index = transactions.findIndex(
      (t) => t.transaction_id === txn.transaction_id,
    );
    set({
      selectedTransaction: txn,
      selectedIndex: index >= 0 ? index : 0,
      entries: [],
    });
  },

  setSelectedIndex: (index) => {
    const { transactions } = get();
    const txn = transactions[index] ?? null;
    set({ selectedIndex: index, selectedTransaction: txn });
  },

  setStatusFilter: (status) => {
    set({ statusFilter: status, selectedIndex: 0, selectedTransaction: null });
  },

  fetchEntries: async (txnId, client) => {
    set({ entriesLoading: true });

    try {
      const entries = await client.get<readonly SnapshotEntry[]>(
        `/api/v2/snapshots/${encodeURIComponent(txnId)}/entries`,
      );
      set({ entries: entries ?? [], entriesLoading: false });
      useUiStore.getState().markDataUpdated("versions");
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to fetch entries");
      set({
        entries: [],
        entriesLoading: false,
        error: message,
      });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  fetchDiff: async (path, version1, version2, transactionId, client) => {
    const requestKey = `${transactionId}\x00${path}\x00${version1 ?? ""}\x00${version2 ?? ""}`;
    set({ diffLoading: true, diffContent: null, error: null, _diffRequestKey: requestKey });

    try {
      const txnParam = `&transaction_id=${encodeURIComponent(transactionId)}`;
      // version1 null = new file (no original), version2 null = deleted file
      const [oldContent, newContent] = await Promise.all([
        version1
          ? client.get<{ content: string }>(
              `/api/v2/files/read?path=${encodeURIComponent(path)}&version=${encodeURIComponent(version1)}&include_metadata=false${txnParam}`,
            ).then((r) => r.content ?? "")
          : Promise.resolve(""),
        version2
          ? client.get<{ content: string }>(
              `/api/v2/files/read?path=${encodeURIComponent(path)}&version=${encodeURIComponent(version2)}&include_metadata=false${txnParam}`,
            ).then((r) => r.content ?? "")
          : Promise.resolve(""),
      ]);

      // Discard stale response if selection changed while this was in-flight
      if (get()._diffRequestKey !== requestKey) return;

      set({
        diffContent: { old: oldContent, new: newContent },
        diffLoading: false,
      });
      useUiStore.getState().markDataUpdated("versions");
    } catch (err) {
      if (get()._diffRequestKey !== requestKey) return;
      const message = err instanceof Error ? err.message : String(err ?? "Failed to fetch diff");
      set({
        diffContent: null,
        diffLoading: false,
        error: message,
      });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  beginTransaction: async (client, description, ttlSeconds) => {
    set({ error: null });

    try {
      const body: Record<string, unknown> = {};
      if (description !== undefined) body["description"] = description;
      if (ttlSeconds !== undefined) body["ttl_seconds"] = ttlSeconds;

      const newTxn = await client.post<Transaction>("/api/v2/snapshots", body);
      await get().fetchTransactions(client);
      // Auto-select the newly created transaction so the editor shows [txn:xxx]
      if (newTxn?.transaction_id) {
        const fresh = get().transactions.find(
          (t) => t.transaction_id === newTxn.transaction_id,
        ) ?? newTxn;
        get().selectTransaction(fresh);
      }
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to begin transaction");
      set({ error: message });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  commitTransaction: async (txnId, client) => {
    set({ error: null });

    try {
      await client.post<Transaction>(
        `/api/v2/snapshots/${encodeURIComponent(txnId)}/commit`,
        {},
      );
      // Clear selection since transaction is no longer active
      set({ selectedTransaction: null });
      await get().fetchTransactions(client);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to commit transaction");
      set({ error: message });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  rollbackTransaction: async (txnId, client) => {
    set({ error: null });

    try {
      await client.post<Transaction>(
        `/api/v2/snapshots/${encodeURIComponent(txnId)}/rollback`,
        {},
      );
      // Clear selection since transaction is no longer active
      set({ selectedTransaction: null });
      await get().fetchTransactions(client);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to rollback transaction");
      set({ error: message });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  fetchConflicts: async (client) => {
    set({ conflictsLoading: true, error: null });

    try {
      const response = await client.get<ConflictsResponse>(
        "/api/v2/sync/conflicts",
      );
      set({
        conflicts: response.conflicts ?? [],
        conflictsLoading: false,
      });
      useUiStore.getState().markDataUpdated("versions");
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err ?? "Failed to fetch conflicts");
      set({
        conflicts: [],
        conflictsLoading: false,
        error: message,
      });
      useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
    }
  },

  toggleConflicts: () => {
    set((state) => ({ showConflicts: !state.showConflicts }));
  },

  clearDiff: () => {
    set({ diffContent: null, diffLoading: false, _diffRequestKey: "" });
  },
}));
