// frontend/src/lib/models/DeploymentModel.svelte.ts
// #region DeploymentModel [C:4] [TYPE Model] [SEMANTICS git,deploy,environment,screen-model]
// @BRIEF State model for deployment modal — load environments, select target, deploy with prod confirmation.
// @INVARIANT Cannot deploy without a selected environment (selectedEnv must be set).
// @INVARIANT Deploy and loading are mutually exclusive — only one operation runs at a time.
// @STATE idle         — Environments loaded, selection ready.
// @STATE loading      — Fetching environments from API.
// @STATE deploying    — Deploy request in flight.
// @STATE confirm_prod — Prod confirmation prompt shown before deployment.
// @ACTION loadEnvironments()          — Fetch available environments from API.
// @ACTION handleDeploy(dashboardId)   — Deploy to selected environment with prod confirmation.
// @ACTION normalizeEnvStage(env)      — Normalize environment stage with legacy fallback.
// @ACTION resolveEnvUrl(env)          — Resolve environment URL from payload shape.
// @RELATION DEPENDS_ON -> [EXT:frontend:gitService]
// @RELATION DEPENDS_ON -> [EXT:frontend:api]
// @RELATION DEPENDS_ON -> [ToastsModule]
// @RELATION DEPENDS_ON -> [GitUtils]
// @REJECTED Inline $state in DeploymentModal.svelte rejected — model extraction enables L1 tests without DOM render.
//   Prod confirmation logic previously duplicated across useGitManager.js and DeploymentModal.svelte
//   is now unified in this model.

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';
import { normalizeEnvStage } from '../../services/git-utils.js';

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

interface EnvironmentItem {
  id: string;
  name?: string;
  stage?: string;
  is_production?: boolean;
  superset_url?: string;
  url?: string;
  [key: string]: unknown;
}

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

export class DeploymentModel {
  // ── Identity ───────────────────────────────────────────────
  /** Dashboard slug or reference for deploy calls. */
  dashboardId: string = $state('');
  /** Source environment ID (current). */
  envId: string | null = $state(null);
  /** Modal visibility controlled externally. */
  show: boolean = $state(false);
  /** Preferred target stage hint (DEV/PREPROD/PROD) from GitFlow. */
  preferredTargetStage: string = $state('');

  // ── Environment List ───────────────────────────────────────
  /** All available environments from the API. */
  environments: EnvironmentItem[] = $state([]);
  /** Selected environment ID for deployment. */
  selectedEnv: string = $state('');
  /** True while fetching environments. */
  loading: boolean = $state(false);
  /** True while deploying. */
  deploying: boolean = $state(false);

  // ── Derived State ──────────────────────────────────────────

  /** Normalized preferred target stage (uppercased). */
  normalizedPreferredStage: string = $derived(String(this.preferredTargetStage || '').toUpperCase());

  /** Environments eligible for deployment (filtered out current env). */
  deploymentCandidates: EnvironmentItem[] = $derived.by(() => {
    const all = this.environments.filter((env: EnvironmentItem) => env?.id !== this.envId);
    if (!this.normalizedPreferredStage) return all;
    const stageMatched = all.filter(
      (env: EnvironmentItem) => normalizeEnvStage(env) === this.normalizedPreferredStage,
    );
    return stageMatched.length > 0 ? stageMatched : all;
  });

  /** Currently selected environment object. */
  selectedEnvironment: EnvironmentItem | null = $derived(
    this.deploymentCandidates.find((env: EnvironmentItem) => env.id === this.selectedEnv) || null,
  );

  /** Stage of the selected environment. */
  selectedEnvironmentStage: string = $derived(
    this.selectedEnvironment ? normalizeEnvStage(this.selectedEnvironment) : '',
  );

  /** True when selected environment is PROD. */
  isProdTarget: boolean = $derived(this.selectedEnvironmentStage === 'PROD');

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

  // ── Environment Utilities ──────────────────────────────────

  /**
   * Normalize environment stage with legacy production fallback.
   * Re-exported from git-utils.js for convenience.
   */
  normalizeEnvStage(env: EnvironmentItem): string {
    return normalizeEnvStage(env);
  }

  /**
   * Resolve environment URL from consolidated or git-specific payload shape.
   */
  resolveEnvUrl(env: EnvironmentItem): string {
    return String(env?.superset_url || env?.url || '');
  }

  // ── Load Environments ──────────────────────────────────────

  /**
   * Fetch available environments from API and select the preferred candidate.
   * @SIDE_EFFECT Sets environments, selectedEnv; shows toast on failure.
   */
  async loadEnvironments(): Promise<void> {
    this.loading = true;
    try {
      this.environments = await api.getEnvironmentsList();
      const candidates = (this.environments || []).filter((env: EnvironmentItem) => env?.id !== this.envId);
      if (this.normalizedPreferredStage) {
        const stageMatched = candidates.filter(
          (env: EnvironmentItem) => normalizeEnvStage(env) === this.normalizedPreferredStage,
        );
        this.selectedEnv = stageMatched[0]?.id || candidates[0]?.id || '';
      } else {
        this.selectedEnv = candidates[0]?.id || '';
      }
    } catch (e: unknown) {
      addToast((tt().migration as Record<string, string>)?.loading_envs_failed || 'Failed to load environments', 'error');
    } finally {
      this.loading = false;
    }
  }

  // ── Deploy ─────────────────────────────────────────────────

  /**
   * Deploy dashboard to the selected environment.
   * Shows a prompt confirmation when targeting PROD.
   * @POST On success: modal closes, success toast shown.
   *        On failure: error toast shown.
   * @SIDE_EFFECT Calls gitService.deploy; shows prompt for PROD.
   */
  async handleDeploy(): Promise<void> {
    if (!this.selectedEnv) return;

    // PROD confirmation prompt
    if (this.isProdTarget) {
      const expected = String(this.dashboardId);
      const confirmation = prompt(
        `Подтвердите deploy в PROD. Введите slug: ${expected}`,
      );
      if (String(confirmation || '').trim() !== expected) {
        addToast('Подтверждение PROD не пройдено. Deploy отменен.', 'error');
        return;
      }
    }

    this.deploying = true;
    try {
      const result: { message?: string } = await gitService.deploy(this.dashboardId, this.selectedEnv, this.envId);
      addToast(result?.message || (tt().git as Record<string, string>)?.deploy_success || 'Deployed successfully', 'success');
      this.show = false;
    } catch (e: unknown) {
      addToast(e instanceof Error ? e.message : 'Deploy failed', 'error');
    } finally {
      this.deploying = false;
    }
  }
}
// #endregion DeploymentModel
