// frontend/src/lib/models/CommitModel.svelte.ts
// #region CommitModel [C:4] [TYPE Model] [SEMANTICS git,commit,message,diff,screen-model]
// @BRIEF State model for commit modal — message generation, status loading, commit with optional push.
// @INVARIANT commit and generateMessage are mutually exclusive — only one runs at a time.
// @STATE idle           — No operation in progress, form ready.
// @STATE loading        — Fetching git status and diff.
// @STATE generating     — AI commit message generation in progress.
// @STATE committing     — Commit (and optional push) in progress.
// @ACTION loadStatus()              — Fetch git status + staged and unstaged diff.
// @ACTION handleGenerateMessage()   — Generate AI commit message via API.
// @ACTION handleCommit()            — Commit changes and optionally push.
// @RELATION DEPENDS_ON -> [EXT:frontend:gitService]
// @RELATION DEPENDS_ON -> [EXT:frontend:api]
// @RELATION DEPENDS_ON -> [ToastsModule]
// @REJECTED Inline $state in CommitModal.svelte rejected — model extraction enables L1 tests without DOM render,
//   follows pattern established by GitManagerModel and GitStatusModel.

import { gitService } from '../../services/gitService.js';
import { api } from '$lib/api.js';
import { addToast } from '$lib/toasts.svelte.js';
import { getT } from '$lib/i18n/index.svelte.js';

// ── Types ─────────────────────────────────────────────────────

interface CommitStatusPayload {
  is_dirty?: boolean;
  current_branch?: string;
  staged_files?: string[];
  modified_files?: string[];
  untracked_files?: string[];
  [key: string]: unknown;
}

/** @returns Current i18n translations. */
function tt(): Record<string, unknown> {
  return getT();
}

export class CommitModel {
  // ── Identity ───────────────────────────────────────────────
  /** Dashboard slug or reference for git API calls. */
  dashboardId: string = $state('');
  /** Environment ID for scoped git operations. */
  envId: string | null = $state(null);
  /** Modal visibility controlled externally. */
  show: boolean = $state(false);

  // ── Commit Message ─────────────────────────────────────────
  /** Current commit message text. */
  message: string = $state('');
  /** True while a commit is in progress. */
  committing: boolean = $state(false);
  /** True while AI message generation is in progress. */
  generatingMessage: boolean = $state(false);
  /** True to auto-push after commit. */
  autoPushAfterCommit: boolean = $state(true);

  // ── Git Status ─────────────────────────────────────────────
  /** Git status payload (is_dirty, staged_files, etc.). */
  status: CommitStatusPayload | null = $state(null);
  /** Combined staged + unstaged diff string. */
  diff: string = $state('');
  /** True while loading status and diff. */
  loading: boolean = $state(false);

  // ── Derived ────────────────────────────────────────────────

  /** Commit button is disabled when there's nothing to commit. */
  canCommit: boolean = $derived.by(() => {
    if (this.committing || this.loading) return false;
    if (!this.message) return false;
    if (!this.status) return false;
    const hasChanges =
      this.status.is_dirty ||
      (Array.isArray(this.status.staged_files) && this.status.staged_files.length > 0);
    return hasChanges;
  });

  constructor({ dashboardId = '', envId = null, show = false }: { dashboardId?: string; envId?: string | null; show?: boolean } = {}) {
    this.dashboardId = dashboardId;
    this.envId = envId;
    this.show = show;
  }

  // ── Status Loading ─────────────────────────────────────────

  /**
   * Fetch current git status and diff for the dashboard.
   * @SIDE_EFFECT Sets status, diff, loading lifecycle.
   */
  async loadStatus(): Promise<void> {
    if (!this.dashboardId || !this.show) return;
    this.loading = true;
    try {
      this.status = await gitService.getStatus(this.dashboardId, this.envId);
      const unstagedDiff: string = await gitService.getDiff(this.dashboardId, null, false, this.envId);
      const stagedDiff: string = await gitService.getDiff(this.dashboardId, null, true, this.envId);

      let combined = '';
      if (stagedDiff) combined += '--- STAGED CHANGES ---\n' + stagedDiff + '\n\n';
      if (unstagedDiff) combined += '--- UNSTAGED CHANGES ---\n' + unstagedDiff;
      this.diff = combined || '';
    } catch (e: unknown) {
      const errMsg = e instanceof Error ? e.message : 'Failed to load status';
      addToast((tt().git as Record<string, string>)?.load_changes_failed || errMsg, 'error');
      this.status = null;
      this.diff = '';
    } finally {
      this.loading = false;
    }
  }

  // ── Generate Message ───────────────────────────────────────

  /**
   * Generate an AI commit message from the dashboard diff.
   * @SIDE_EFFECT POSTs to /git/repositories/{ref}/generate-message.
   */
  async handleGenerateMessage(): Promise<void> {
    this.generatingMessage = true;
    try {
      const data: { message?: string } = await api.postApi(
        `/git/repositories/${encodeURIComponent(String(this.dashboardId))}/generate-message${
          this.envId ? `?env_id=${encodeURIComponent(String(this.envId))}` : ''
        }`,
        undefined,
        { suppressToast: true },
      );
      this.message = data?.message || '';
      addToast((tt().git as Record<string, string>)?.commit_message_generated || 'Commit message generated', 'success');
    } catch (e: unknown) {
      addToast(e instanceof Error ? e.message : (tt().git as Record<string, string>)?.commit_message_failed || 'Failed to generate message', 'error');
    } finally {
      this.generatingMessage = false;
    }
  }

  // ── Commit ─────────────────────────────────────────────────

  /**
   * Commit changes and optionally push to remote.
   * @PRE message is non-empty; status has changes.
   * @POST On success: message cleared, toast shown, modal closed.
   */
  async handleCommit(): Promise<void> {
    if (!this.message || !this.canCommit) return;
    this.committing = true;
    try {
      await gitService.commit(this.dashboardId, this.message, [], this.envId);
      if (this.autoPushAfterCommit) {
        await gitService.push(this.dashboardId, this.envId);
        addToast((tt().git as Record<string, string>)?.commit_and_push_success || 'Committed and pushed', 'success');
      } else {
        addToast((tt().git as Record<string, string>)?.commit_success || 'Committed successfully', 'success');
      }
      this.show = false;
      this.message = '';
    } catch (e: unknown) {
      addToast(e instanceof Error ? e.message : 'Commit failed', 'error');
    } finally {
      this.committing = false;
    }
  }
}
// #endregion CommitModel
