import { useCallback, useReducer } from "react";
import type {
  MigrationPlan,
  MappingRow,
} from "../services/categoryMappingService";
import type { BackupResult } from "../services/categoryBackupService";
import type { MigrationOutcome } from "../services/categoryMigrationService";

// -----------------------------------------------------------------------------
// useCategoryMigration — useReducer-backed state machine for the 3-step page.
//
// State model:
//   - step         : which screen is rendered (discover → simulate → consent → running → success | error)
//   - plan         : MigrationPlan produced by categoryMappingService (memory only)
//   - backup       : BackupResult once the pre-migration SREF is written (null otherwise)
//   - outcome      : MigrationOutcome produced by applyMigration (null otherwise)
//   - errors       : list of error messages collected during the flow
//   - unresolved   : number of "🟠 needs review" rows still blocking the next step
//
// The hook exposes pure action dispatchers — it does NOT call services. The
// page component is responsible for calling computeMigrationPlan,
// createPreMigrationBackup and applyMigration, then dispatching the
// corresponding actions here. Keeping the reducer service-free lets us test
// the state transitions without mocking Tauri.
// -----------------------------------------------------------------------------

export type MigrationStep =
  | "discover"
  | "simulate"
  | "consent"
  | "running"
  | "success"
  | "error";

export interface MigrationState {
  step: MigrationStep;
  plan: MigrationPlan | null;
  backup: BackupResult | null;
  outcome: MigrationOutcome | null;
  /** Number of unresolved rows (confidence === 'none' && still missing v1TargetId). */
  unresolved: number;
  errors: string[];
  /** Selected mapping row id to show in the TransactionPreviewPanel. Null = closed. */
  selectedRowV2Id: number | null;
}

export type MigrationAction =
  | { type: "LOAD_PLAN"; plan: MigrationPlan }
  | { type: "RESOLVE_ROW"; v2CategoryId: number; v1TargetId: number; v1TargetName: string }
  | { type: "SELECT_ROW"; v2CategoryId: number | null }
  | { type: "GO_NEXT" }
  | { type: "GO_BACK" }
  | { type: "START_RUN" }
  | { type: "SET_BACKUP"; backup: BackupResult }
  | { type: "SET_OUTCOME"; outcome: MigrationOutcome }
  | { type: "FAIL"; error: string }
  | { type: "RESET" };

export const INITIAL_STATE: MigrationState = {
  step: "discover",
  plan: null,
  backup: null,
  outcome: null,
  unresolved: 0,
  errors: [],
  selectedRowV2Id: null,
};

function countUnresolved(rows: MappingRow[]): number {
  let n = 0;
  for (const r of rows) {
    if (r.v1TargetId === null || r.v1TargetId === undefined) n++;
  }
  return n;
}

function nextStep(current: MigrationStep): MigrationStep {
  switch (current) {
    case "discover":
      return "simulate";
    case "simulate":
      return "consent";
    case "consent":
      return "running";
    case "running":
      return "success";
    default:
      return current;
  }
}

function previousStep(current: MigrationStep): MigrationStep {
  switch (current) {
    case "simulate":
      return "discover";
    case "consent":
      return "simulate";
    case "error":
      return "consent";
    default:
      return current;
  }
}

export function migrationReducer(
  state: MigrationState,
  action: MigrationAction,
): MigrationState {
  switch (action.type) {
    case "LOAD_PLAN": {
      return {
        ...state,
        plan: action.plan,
        unresolved: countUnresolved(action.plan.rows),
        errors: [],
      };
    }

    case "RESOLVE_ROW": {
      if (state.plan === null) return state;
      // A resolved target can land on a seeded row (plan.rows) OR a custom
      // category (plan.preserved) — the latter is the "merge a custom into a
      // standard leaf" path (#259). Apply the target in whichever bucket holds
      // the row; it is a no-op for the other one.
      const applyTarget = (r: MappingRow): MappingRow =>
        r.v2CategoryId === action.v2CategoryId
          ? {
              ...r,
              v1TargetId: action.v1TargetId,
              v1TargetName: action.v1TargetName,
              // Resolving a row manually bumps the confidence badge to "medium"
              // so the table reflects the decision. Reason is left as-is so the
              // tooltip still explains what the algorithm thought.
              confidence: r.confidence === "none" ? "medium" : r.confidence,
            }
          : r;
      const rows = state.plan.rows.map(applyTarget);
      const preserved = state.plan.preserved.map(applyTarget);
      const plan: MigrationPlan = {
        ...state.plan,
        rows,
        preserved,
        unresolved: rows.filter((r) => r.v1TargetId === null),
      };
      return {
        ...state,
        plan,
        // `unresolved` gates the Next button and counts seeded rows ONLY. A
        // custom left unmerged is a legitimate choice and must never block the
        // wizard.
        unresolved: countUnresolved(rows),
      };
    }

    case "SELECT_ROW":
      return { ...state, selectedRowV2Id: action.v2CategoryId };

    case "GO_NEXT": {
      // Guard: simulate -> consent requires unresolved === 0.
      if (state.step === "simulate" && state.unresolved > 0) return state;
      return { ...state, step: nextStep(state.step) };
    }

    case "GO_BACK":
      return { ...state, step: previousStep(state.step) };

    case "START_RUN":
      return { ...state, step: "running", errors: [] };

    case "SET_BACKUP":
      return { ...state, backup: action.backup };

    case "SET_OUTCOME": {
      const nextStepName: MigrationStep = action.outcome.succeeded
        ? "success"
        : "error";
      return {
        ...state,
        outcome: action.outcome,
        step: nextStepName,
        errors: action.outcome.error
          ? [...state.errors, action.outcome.error]
          : state.errors,
      };
    }

    case "FAIL":
      return {
        ...state,
        step: "error",
        errors: [...state.errors, action.error],
      };

    case "RESET":
      return INITIAL_STATE;

    default:
      return state;
  }
}

export interface UseCategoryMigrationResult {
  state: MigrationState;
  loadPlan: (plan: MigrationPlan) => void;
  resolveRow: (v2CategoryId: number, v1TargetId: number, v1TargetName: string) => void;
  selectRow: (v2CategoryId: number | null) => void;
  goNext: () => void;
  goBack: () => void;
  startRun: () => void;
  setBackup: (backup: BackupResult) => void;
  setOutcome: (outcome: MigrationOutcome) => void;
  fail: (error: string) => void;
  reset: () => void;
}

export function useCategoryMigration(): UseCategoryMigrationResult {
  const [state, dispatch] = useReducer(migrationReducer, INITIAL_STATE);

  const loadPlan = useCallback((plan: MigrationPlan) => {
    dispatch({ type: "LOAD_PLAN", plan });
  }, []);

  const resolveRow = useCallback(
    (v2CategoryId: number, v1TargetId: number, v1TargetName: string) => {
      dispatch({ type: "RESOLVE_ROW", v2CategoryId, v1TargetId, v1TargetName });
    },
    [],
  );

  const selectRow = useCallback((v2CategoryId: number | null) => {
    dispatch({ type: "SELECT_ROW", v2CategoryId });
  }, []);

  const goNext = useCallback(() => dispatch({ type: "GO_NEXT" }), []);
  const goBack = useCallback(() => dispatch({ type: "GO_BACK" }), []);
  const startRun = useCallback(() => dispatch({ type: "START_RUN" }), []);

  const setBackup = useCallback((backup: BackupResult) => {
    dispatch({ type: "SET_BACKUP", backup });
  }, []);

  const setOutcome = useCallback((outcome: MigrationOutcome) => {
    dispatch({ type: "SET_OUTCOME", outcome });
  }, []);

  const fail = useCallback((error: string) => {
    dispatch({ type: "FAIL", error });
  }, []);

  const reset = useCallback(() => dispatch({ type: "RESET" }), []);

  return {
    state,
    loadPlan,
    resolveRow,
    selectRow,
    goNext,
    goBack,
    startRun,
    setBackup,
    setOutcome,
    fail,
    reset,
  };
}
