// #region Dashboards.GitActionsModel [C:4] [TYPE Model] [SEMANTICS dashboard,git,actions,sync,commit]
// @ingroup Dashboards
// @BRIEF Git operations state and actions for the dashboard hub. Owned by Dashboards.HubModel.
// @INVARIANT gitBusyIds prevents concurrent operations on the same dashboard.
// @INVARIANT gitResolvedIds prevents duplicate fetches of the same dashboard git status within a load cycle.
// @STATE idle     — No git operation in progress.
// @STATE loading  — Fetching git status for visible dashboards.
// @STATE busy     — Git action (init/sync/commit/pull/push) in progress.
// @STATE error    — Last git operation failed (toast shown, state cleared).
// @ACTION isGitBusy(id)                       — Whether a dashboard is busy with a git operation.
// @ACTION setGitBusy(id, busy)                — Sets/resets busy flag for a dashboard.
// @ACTION ensureGitConfigs()                  — Returns cached or fetches git configs.
// @ACTION normalizeRepositoryStatusPayload(s) — Normalizes API status to GitState.
// @ACTION fetchDashboardGitStatusesBatch(batch) — Batch-fetches git statuses for pending dashboards.
// @ACTION handleGitInit(dashboard)             — Initializes a git repo for a dashboard.
// @ACTION handleGitSync(dashboard, env)        — Syncs dashboard with git remote.
// @ACTION handleGitCommit(dashboard, env)      — Commits changes for a dashboard.
// @ACTION handleGitPull(dashboard, env)        — Pulls latest from git remote.
// @ACTION handleGitPush(dashboard, env)        — Pushes to git remote.
// @ACTION getGitSummaryLabel(dashboard)        — Returns human-readable git summary label.
// @ACTION toggleActionDropdown(id, event)      — Toggles the action dropdown for a dashboard.
// @ACTION closeActionDropdown()                — Closes the action dropdown.
// @RELATION CALLS -> [gitService]
// @RELATION CALLED_BY -> [Dashboards.HubModel]

import { SvelteSet } from "svelte/reactivity";
import { gitService } from "../../services/gitService.js";
import { addToast } from "$lib/toasts.svelte.js";
import { t } from "$lib/i18n/index.svelte.js";

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

interface DashboardRow {
  id: string; title: string; slug: string;
  changedOn: string | null; changedOnLabel: string;
  owners: string[]; actorLabel: string;
  git: { status: string; branch: string | null; hasRepo: boolean | null; hasChangesForCommit: boolean };
  lastTask: { status: string | null; validationStatus: string; id: string } | null;
  actions: string[];
}

interface GitState { status: string; branch: string | null; hasRepo: boolean; hasChangesForCommit: boolean }

// ═══ Model ═══════════════════════════════════════════════════════

export class DashboardsGitActionsModel {
  // ── Git state ─────────────────────────────────────────────────
  openActionDropdown: string | null = $state(null);
  gitBusyIds: Set<string> = $state(new SvelteSet());
  gitResolvedIds: Set<string> = $state(new SvelteSet());
  gitLoadingIds: Set<string> = $state(new SvelteSet());
  cachedGitConfigs: any[] = $state([]);

  /**
   * Callback invoked when a dashboard's git state changes.
   * Set by the parent (Dashboards.HubModel) to propagate git state
   * updates into the parent's dashboard arrays.
   */
  onGitStateUpdate?: (dashboardId: string, nextGit: Partial<GitState>) => void;

  // ═══ Git helpers ═══════════════════════════════════════════════

  getGitSummaryLabel(dashboard: DashboardRow): string {
    if (dashboard.git?.hasRepo === null) return t.common?.loading || "Loading";
    if (!dashboard.git?.hasRepo) return t.dashboard?.status_no_repo || "No Repo";
    return dashboard.git?.hasChangesForCommit ? t.dashboard?.status_changes || "Diff" : t.dashboard?.status_no_changes || "Synced";
  }

  isGitBusy(dashboardId: string): boolean { return this.gitBusyIds.has(dashboardId); }

  setGitBusy(dashboardId: string, busy: boolean): void {
    if (busy) this.gitBusyIds.add(dashboardId); else this.gitBusyIds.delete(dashboardId);
    this.gitBusyIds = new SvelteSet(this.gitBusyIds);
  }

  async ensureGitConfigs(): Promise<any[]> {
    if (this.cachedGitConfigs.length > 0) return this.cachedGitConfigs;
    this.cachedGitConfigs = await gitService.getConfigs();
    return this.cachedGitConfigs;
  }

  normalizeRepositoryStatusPayload(status: any): GitState {
    const syncState = String(status?.sync_state || "").toUpperCase();
    const syncStatus = String(status?.sync_status || "").toUpperCase();
    const explicitHasRepo = status?.has_repo;
    const hasRepo = explicitHasRepo === true ? true : explicitHasRepo === false ? false : !(syncState === "NO_REPO" || syncStatus === "NO_REPO");
    if (!hasRepo) return { status: "no_repo", branch: null, hasRepo: false, hasChangesForCommit: false };
    const hasChanges = syncState === "CHANGES" || syncStatus === "DIFF" || Boolean(status?.is_dirty) ||
      (status?.untracked_files?.length || 0) > 0 || (status?.modified_files?.length || 0) > 0 || (status?.staged_files?.length || 0) > 0;
    return { status: hasChanges ? "diff" : "ok", branch: status?.current_branch || status?.branch || null, hasRepo: true, hasChangesForCommit: hasChanges };
  }

  async fetchDashboardGitStatusesBatch(dashboardIds: string[], force = false): Promise<void> {
    const uniqueIds = Array.from(new SvelteSet(dashboardIds));
    if (uniqueIds.length === 0) return;
    if (force) { uniqueIds.forEach(id => this.gitResolvedIds.delete(id)); this.gitResolvedIds = new SvelteSet(this.gitResolvedIds); }
    const pendingIds = uniqueIds.filter(id => !this.gitResolvedIds.has(id) && !this.gitLoadingIds.has(id));
    if (pendingIds.length === 0) return;
    pendingIds.forEach(id => this.gitLoadingIds.add(id));
    this.gitLoadingIds = new SvelteSet(this.gitLoadingIds);
    try {
      const batchResult = await gitService.getStatusesBatch(pendingIds);
      const statuses = batchResult?.statuses || {};
      pendingIds.forEach(id => {
        const st = statuses[id] ?? statuses[String(id)];
        const nextGit = st ? this.normalizeRepositoryStatusPayload(st) : { status: "no_repo", branch: null, hasRepo: false, hasChangesForCommit: false };
        this.onGitStateUpdate?.(id, nextGit);
      });
    } catch {
      pendingIds.forEach(id => {
        this.onGitStateUpdate?.(id, { status: "no_repo", branch: null, hasRepo: false, hasChangesForCommit: false });
      });
    } finally {
      pendingIds.forEach(id => { this.gitLoadingIds.delete(id); this.gitResolvedIds.add(id); });
      this.gitLoadingIds = new SvelteSet(this.gitLoadingIds);
      this.gitResolvedIds = new SvelteSet(this.gitResolvedIds);
    }
  }

  async handleGitInit(dashboard: DashboardRow): Promise<void> {
    this.setGitBusy(dashboard.id, true);
    try {
      const configs = await this.ensureGitConfigs();
      if (!configs.length) { addToast(t.git?.no_servers_configured || "No Git config found", "error"); return; }
      const config = configs[0];
      const defaultRemote = config?.default_repository ? `${String(config.url || "").replace(/\/$/, "")}/${config.default_repository}.git` : "";
      const remoteUrl = prompt(t.git?.remote_url || "Remote URL", defaultRemote);
      if (!remoteUrl) return;
      await gitService.initRepository(dashboard.id, config.id, remoteUrl.trim());
      addToast(t.git?.init_success || "Repository initialized", "success");
      await this.fetchDashboardGitStatusesBatch([dashboard.id], true);
    } catch (err: any) { addToast(err?.message || "Git init failed", "error"); }
    finally { this.setGitBusy(dashboard.id, false); }
  }

  async handleGitSync(dashboard: DashboardRow, selectedEnv: string | null): Promise<void> {
    this.setGitBusy(dashboard.id, true);
    try { await gitService.sync(dashboard.id, selectedEnv || null); addToast(t.git?.sync_success || "Synced", "success"); await this.fetchDashboardGitStatusesBatch([dashboard.id], true); }
    catch (err: any) { addToast(err?.message || "Git sync failed", "error"); }
    finally { this.setGitBusy(dashboard.id, false); }
  }

  async handleGitCommit(dashboard: DashboardRow, selectedEnv: string | null): Promise<void> {
    if (!dashboard.git?.hasRepo) { addToast(t.git?.not_linked || "Repository not linked", "error"); return; }
    if (!dashboard.git?.hasChangesForCommit) { addToast(t.git?.nothing_to_commit || "No changes to commit", "error"); return; }
    const message = prompt(t.git?.commit_message || "Commit message", `Update dashboard ${dashboard.title}`);
    if (!message?.trim()) return;
    this.setGitBusy(dashboard.id, true);
    try { await gitService.commit(dashboard.slug || dashboard.id, message.trim()); addToast(t.git?.commit_success || "Committed", "success"); await this.fetchDashboardGitStatusesBatch([dashboard.id], true); }
    catch (err: any) { addToast(err?.message || "Git commit failed", "error"); }
    finally { this.setGitBusy(dashboard.id, false); }
  }

  async handleGitPull(dashboard: DashboardRow, selectedEnv: string | null): Promise<void> {
    if (!dashboard.git?.hasRepo) return;
    this.setGitBusy(dashboard.id, true);
    try { await gitService.pull(dashboard.slug || dashboard.id, selectedEnv || null); addToast(t.git?.pull_success || "Pulled", "success"); await this.fetchDashboardGitStatusesBatch([dashboard.id], true); }
    catch (err: any) { addToast(err?.message || "Git pull failed", "error"); }
    finally { this.setGitBusy(dashboard.id, false); }
  }

  async handleGitPush(dashboard: DashboardRow, selectedEnv: string | null): Promise<void> {
    if (!dashboard.git?.hasRepo) return;
    this.setGitBusy(dashboard.id, true);
    try { await gitService.push(dashboard.slug || dashboard.id, selectedEnv || null); addToast(t.git?.push_success || "Pushed", "success"); await this.fetchDashboardGitStatusesBatch([dashboard.id], true); }
    catch (err: any) { addToast(err?.message || "Git push failed", "error"); }
    finally { this.setGitBusy(dashboard.id, false); }
  }

  toggleActionDropdown(id: string | null, event: Event): void {
    event.stopPropagation();
    this.openActionDropdown = this.openActionDropdown === id ? null : id;
  }
  closeActionDropdown(): void { this.openActionDropdown = null; }
}
// #endregion Dashboards.GitActionsModel
