// frontend/src/lib/models/MappingsModel.svelte.ts
// #region MappingsModel [C:4] [TYPE Model] [SEMANTICS mapping,database,screen-model]
// @BRIEF State model for the standalone database mappings page.
// @REJECTED Inline $state + handler functions in migration/mappings/+page.svelte was rejected — the RSM pattern (semantics-svelte §IIIa) requires model-first architecture with L1 testability.
// @INVARIANT Changing source environment resets target databases, mappings, suggestions, and targetEnvId.
// @INVARIANT Changing target environment resets target databases, mappings, and suggestions.
// @ACTION loadEnvironments() — loads environments, pre-fills source from active context
// @ACTION selectSourceEnv(id) — selects source, resets dependent state
// @ACTION selectTargetEnv(id) — selects target, resets mapped state
// @ACTION fetchDatabases() — loads source/target databases, existing mappings, suggestions
// @ACTION saveMapping(srcUuid, tgtUuid) — saves a mapping pair
// @RELATION DEPENDS_ON -> [EXT:frontend:api]
// @RELATION DEPENDS_ON -> [EXT:frontend:environmentContextStore]
import { api } from "$lib/api.js";
import { environmentContextStore } from "$lib/stores/environmentContext.svelte.js";

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

interface Environment {
  id: string;
  name: string;
  [key: string]: unknown;
}

interface Database {
  uuid: string;
  database_name: string;
  [key: string]: unknown;
}

interface Mapping {
  id?: string | number;
  source_db_uuid?: string;
  target_db_uuid?: string;
  source_db_name?: string;
  target_db_name?: string;
  [key: string]: unknown;
}

interface Suggestion {
  source_db_uuid?: string;
  target_db_uuid?: string;
  confidence?: number;
  [key: string]: unknown;
}

export class MappingsModel {
  // ── Atoms (reactive state) ──────────────────────────────────────

  /** Environments available for source/target selection. */
  environments: Environment[] = $state([]);

  /** Currently selected source environment ID. */
  sourceEnvId: string = $state("");

  /** Currently selected target environment ID. */
  targetEnvId: string = $state("");

  /** Databases from the source environment. */
  sourceDatabases: Database[] = $state([]);

  /** Databases from the target environment. */
  targetDatabases: Database[] = $state([]);

  /** Saved mapping pairs between source and target databases. */
  mappings: Mapping[] = $state([]);

  /** AI-suggested mapping pairs. */
  suggestions: Suggestion[] = $state([]);

  /** True while environments are being fetched on mount. */
  loading: boolean = $state(true);

  /** True while databases/mappings/suggestions are being fetched. */
  fetchingDbs: boolean = $state(false);

  /** Error message from last failed API call. */
  error: string = $state("");

  /** Success confirmation message (e.g. after saving a mapping). */
  success: string = $state("");

  // ── Actions ─────────────────────────────────────────────────────

  /**
   * Load environments list. Pre-fills source from active context.
   * @POST loading=false and environments populated on success, or error set on failure.
   */
  async loadEnvironments(): Promise<void> {
    this.loading = true;
    this.error = "";
    this.success = "";
    try {
      this.environments = await api.getEnvironmentsList();
      const activeEnvId = environmentContextStore.value.selectedEnvId;
      if (activeEnvId && this.environments.some((env: Environment) => env.id === activeEnvId)) {
        this.sourceEnvId = activeEnvId;
      }
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to load environments";
    } finally {
      this.loading = false;
    }
  }

  /**
   * Select source environment. Resets all dependent state.
   * @INVARIANT Resets target env ID, sourceDatabases, targetDatabases, mappings, suggestions.
   */
  selectSourceEnv(id: string): void {
    if (id === this.sourceEnvId) return;
    this.sourceEnvId = id;
    this.targetEnvId = "";
    this.sourceDatabases = [];
    this.targetDatabases = [];
    this.mappings = [];
    this.suggestions = [];
    this.error = "";
    this.success = "";
  }

  /**
   * Select target environment. Resets mapped database state.
   * @INVARIANT Resets targetDatabases, mappings, suggestions.
   */
  selectTargetEnv(id: string): void {
    if (id === this.targetEnvId) return;
    this.targetEnvId = id;
    this.targetDatabases = [];
    this.mappings = [];
    this.suggestions = [];
    this.error = "";
    this.success = "";
  }

  /**
   * Fetch databases, mappings, and AI suggestions for current env pair.
   * @PRE sourceEnvId and targetEnvId are both non-empty.
   * @POST fetchingDbs=false and sourceDatabases/targetDatabases/mappings/suggestions updated or error set.
   */
  async fetchDatabases(): Promise<void> {
    if (!this.sourceEnvId || !this.targetEnvId) return;
    this.fetchingDbs = true;
    this.error = "";
    this.success = "";
    try {
      const [src, tgt, maps, sugs] = await Promise.all([
        api.requestApi(`/environments/${this.sourceEnvId}/databases`),
        api.requestApi(`/environments/${this.targetEnvId}/databases`),
        api.requestApi(
          `/mappings?source_env_id=${this.sourceEnvId}&target_env_id=${this.targetEnvId}`,
        ),
        api.postApi("/mappings/suggest", {
          source_env_id: this.sourceEnvId,
          target_env_id: this.targetEnvId,
        }),
      ]);
      this.sourceDatabases = src;
      this.targetDatabases = tgt;
      this.mappings = maps;
      this.suggestions = sugs;
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to fetch databases";
    } finally {
      this.fetchingDbs = false;
    }
  }

  /**
   * Save a database mapping between source and target.
   * @INVARIANT Replaces existing mapping for the same source database UUID.
   * @POST Mapping persisted; local mappings array updated; success or error feedback set.
   */
  async saveMapping(sourceUuid: string, targetUuid: string): Promise<void> {
    const sDb = this.sourceDatabases.find((d: Database) => d.uuid === sourceUuid);
    const tDb = this.targetDatabases.find((d: Database) => d.uuid === targetUuid);
    if (!sDb || !tDb) return;
    try {
      const savedMapping = await api.postApi("/mappings", {
        source_env_id: this.sourceEnvId,
        target_env_id: this.targetEnvId,
        source_db_uuid: sourceUuid,
        target_db_uuid: targetUuid,
        source_db_name: sDb.database_name,
        target_db_name: tDb.database_name,
      });
      this.mappings = [
        ...this.mappings.filter((m: Mapping) => m.source_db_uuid !== sourceUuid),
        savedMapping,
      ];
      this.success = "Mapping saved successfully";
      this.error = "";
    } catch (e: unknown) {
      this.error = e instanceof Error ? e.message : "Failed to save mapping";
      this.success = "";
    }
  }
}
// #endregion MappingsModel
