<template>
  <div class="flex h-full min-h-0 flex-col">
    <EditorTabs />

    <template v-if="activeTab">
      <!-- barre : bascule éditeur/diff + chemin + save -->
      <div class="flex items-center gap-2 border-b border-border px-2 py-1 text-xs">
        <SegmentedControl v-model="viewModel" :options="viewOptions" />
        <span class="min-w-0 flex-1 truncate font-mono text-fg-subtle" :title="activeTab.file">{{ activeTab.file }}</span>
        <BaseButton
          v-if="activeTab.view === 'editor'"
          size="sm"
          variant="ghost"
          :icon="Save"
          :loading="saving"
          :disabled="!ide.isDirty(activeTab.id) || !ready"
          @click="save"
        >
          {{ t('editor.save') }}
        </BaseButton>
      </div>

      <!-- bannière de conflit (le fichier a changé sur le disque depuis le chargement) -->
      <div
        v-if="conflict && activeTab.view === 'editor'"
        class="flex flex-wrap items-center gap-2 border-b border-warn/40 bg-warn/10 px-3 py-1.5 text-xs text-warn"
      >
        <TriangleAlert :size="14" /> {{ t('editor.conflict') }}
        <div class="ml-auto flex items-center gap-2">
          <BaseButton size="sm" variant="ghost" @click="reload">{{ t('editor.reload') }}</BaseButton>
          <BaseButton size="sm" variant="danger" :loading="saving" @click="overwrite">{{ t('editor.overwrite') }}</BaseButton>
        </div>
      </div>
      <p v-if="loadError && activeTab.view === 'editor'" class="px-3 py-1 text-xs text-danger">{{ loadError }}</p>

      <div class="relative min-h-0 flex-1">
        <div v-show="activeTab.view === 'editor'" ref="host" class="h-full min-h-0" />
        <DiffViewer
          v-if="activeTab.view === 'diff'"
          :key="`diff:${activeTab.id}`"
          :repo-id="activeTab.repoId"
          :wt="activeTab.wtPath"
          :file="activeTab.file"
          :staged="!!activeTab.diffStaged"
          :version="0"
          class="h-full overflow-auto"
        />
      </div>
    </template>

    <EmptyState v-else :icon="FileCode" :title="t('ide.noFileOpen')" :hint="t('ide.noFileHint')" class="m-6" />
  </div>
</template>

<script setup lang="ts">
// Zone centrale de l'IDE : UNE seule instance Monaco partagée entre tous les onglets, avec un cache
// de modèles (préserve contenu, historique undo et position du curseur au changement d'onglet).
// Les onglets « diff » réutilisent DiffViewer. Le dirty est remonté au store IDE.
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { FileCode, Save, TriangleAlert } from '@lucide/vue';
import type * as Monaco from 'monaco-editor';
import { useIdeStore, type EditorTab } from '../../stores/ide';
import { gitApi } from '../../lib/git-api';
import { ApiError } from '../../lib/api';
import { loadMonaco } from '../../composables/useMonaco';
import { resolvedTheme } from '../../lib/theme';
import EditorTabs from './EditorTabs.vue';
import DiffViewer from '../workspace/DiffViewer.vue';
import SegmentedControl from '../ui/SegmentedControl.vue';
import BaseButton from '../ui/BaseButton.vue';
import EmptyState from '../ui/EmptyState.vue';

interface ModelEntry {
  model: Monaco.editor.ITextModel;
  baseMtime: number | undefined;
  savedContent: string;
}

const { t } = useI18n();
const ide = useIdeStore();
const host = useTemplateRef<HTMLDivElement>('host');

const ready = ref(false);
const saving = ref(false);
const conflict = ref(false);
const loadError = ref<string | null>(null);

let monaco: typeof Monaco | null = null;
const editor = shallowRef<Monaco.editor.IStandaloneCodeEditor | null>(null);
const entries = new Map<string, ModelEntry>();
const viewStates = new Map<string, Monaco.editor.ICodeEditorViewState | null>();
let shownTabId: string | null = null;
let disposed = false;

const activeTab = computed(() => ide.activeTab);
const viewOptions = computed(() => [
  { value: 'editor', label: t('workspace.editor') },
  { value: 'diff', label: t('workspace.diff') },
]);
const viewModel = computed<string>({
  get: () => activeTab.value?.view ?? 'editor',
  set: (v) => {
    if (activeTab.value) ide.setTabView(activeTab.value.id, v === 'diff' ? 'diff' : 'editor', activeTab.value.diffStaged);
  },
});

onMounted(async () => {
  monaco = await loadMonaco();
  if (disposed || !host.value) return;
  editor.value = monaco.editor.create(host.value, {
    value: '',
    theme: resolvedTheme.value === 'light' ? 'arboretum-light' : 'arboretum-dark',
    automaticLayout: true,
    minimap: { enabled: false },
    wordWrap: 'on',
    fontSize: 13,
    fontFamily: "'JetBrains Mono Variable', ui-monospace, 'Fira Code', Menlo, Consolas, monospace",
    scrollBeyondLastLine: false,
    tabSize: 2,
  });
  // Re-mesure après chargement de la fonte web (sinon curseur/colonnes décalés).
  void document.fonts?.ready.then(() => {
    if (!disposed) monaco?.editor.remeasureFonts();
  });
  editor.value.onDidChangeModelContent(() => {
    if (!shownTabId) return;
    const entry = entries.get(shownTabId);
    if (entry) ide.setTabDirty(shownTabId, entry.model.getValue() !== entry.savedContent);
  });
  editor.value.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => void save());
  ready.value = true;
  if (activeTab.value && activeTab.value.view === 'editor') await showTab(activeTab.value);
});

async function ensureEntry(tab: EditorTab): Promise<ModelEntry | null> {
  const cached = entries.get(tab.id);
  if (cached) return cached;
  if (!monaco) return null;
  const res = await gitApi.readFile(tab.repoId, tab.wtPath, tab.file);
  if (disposed) return null;
  const uri = monaco.Uri.parse(`inmemory://arb/${encodeURIComponent(tab.id)}`);
  const model = monaco.editor.getModel(uri) ?? monaco.editor.createModel(res.content, res.language, uri);
  const entry: ModelEntry = { model, baseMtime: res.mtime, savedContent: res.content };
  entries.set(tab.id, entry);
  return entry;
}

async function showTab(tab: EditorTab): Promise<void> {
  if (!editor.value) return;
  loadError.value = null;
  conflict.value = false;
  // sauvegarde de la position/scroll de l'onglet quitté
  if (shownTabId && shownTabId !== tab.id) viewStates.set(shownTabId, editor.value.saveViewState());
  try {
    const entry = await ensureEntry(tab);
    if (!entry || disposed || !editor.value) return;
    editor.value.setModel(entry.model);
    const vs = viewStates.get(tab.id);
    if (vs) editor.value.restoreViewState(vs);
    shownTabId = tab.id;
    ide.setTabDirty(tab.id, entry.model.getValue() !== entry.savedContent);
    requestAnimationFrame(() => editor.value?.layout());
    editor.value.focus();
  } catch (err) {
    loadError.value = err instanceof Error ? err.message : String(err);
  }
}

async function save(): Promise<void> {
  const tab = activeTab.value;
  if (!tab || tab.view !== 'editor' || saving.value || !ide.isDirty(tab.id)) return;
  const entry = entries.get(tab.id);
  if (!entry) return;
  saving.value = true;
  conflict.value = false;
  try {
    const content = entry.model.getValue();
    const res = await gitApi.writeFile(tab.repoId, tab.wtPath, tab.file, content, entry.baseMtime);
    entry.baseMtime = res.mtime;
    entry.savedContent = content;
    ide.setTabDirty(tab.id, false);
  } catch (err) {
    if (err instanceof ApiError && err.code === 'STALE_FILE') conflict.value = true;
    else loadError.value = err instanceof Error ? err.message : String(err);
  } finally {
    saving.value = false;
  }
}

async function overwrite(): Promise<void> {
  const tab = activeTab.value;
  if (!tab || saving.value) return;
  const entry = entries.get(tab.id);
  if (!entry) return;
  saving.value = true;
  try {
    const content = entry.model.getValue();
    const res = await gitApi.writeFile(tab.repoId, tab.wtPath, tab.file, content); // sans baseMtime : écrase
    entry.baseMtime = res.mtime;
    entry.savedContent = content;
    ide.setTabDirty(tab.id, false);
    conflict.value = false;
  } catch (err) {
    loadError.value = err instanceof Error ? err.message : String(err);
  } finally {
    saving.value = false;
  }
}

async function reload(): Promise<void> {
  const tab = activeTab.value;
  if (!tab) return;
  const entry = entries.get(tab.id);
  if (!entry) return;
  try {
    const res = await gitApi.readFile(tab.repoId, tab.wtPath, tab.file);
    entry.model.setValue(res.content);
    entry.baseMtime = res.mtime;
    entry.savedContent = res.content;
    conflict.value = false;
    ide.setTabDirty(tab.id, false);
  } catch (err) {
    loadError.value = err instanceof Error ? err.message : String(err);
  }
}

// changement d'onglet actif -> affiche le bon modèle (si vue éditeur).
watch(
  () => [activeTab.value?.id, activeTab.value?.view] as const,
  ([, view]) => {
    if (ready.value && activeTab.value && view === 'editor') void showTab(activeTab.value);
  },
);

// bascule de thème -> réapplique le thème Monaco (setTheme est global et idempotent).
watch(resolvedTheme, (r) => {
  monaco?.editor.setTheme(r === 'light' ? 'arboretum-light' : 'arboretum-dark');
});

// onglets fermés -> dispose leurs modèles et view states (évite les fuites mémoire).
watch(
  () => ide.editorTabs.map((tb) => tb.id),
  (ids) => {
    const alive = new Set(ids);
    for (const [id, entry] of entries) {
      if (!alive.has(id)) {
        entry.model.dispose();
        entries.delete(id);
        viewStates.delete(id);
        if (shownTabId === id) shownTabId = null;
      }
    }
  },
);

onBeforeUnmount(() => {
  disposed = true;
  for (const entry of entries.values()) entry.model.dispose();
  entries.clear();
  viewStates.clear();
  editor.value?.dispose();
});
</script>
