<template>
  <div class="relative h-full min-h-0 bg-[#09090b]">
    <div ref="container" class="h-full w-full" />
    <div
      v-if="!controlling && !ended"
      class="pointer-events-none absolute top-2 right-4 rounded bg-zinc-800/90 px-2 py-0.5 text-xs text-amber-300"
    >
      {{ t('terminal.observer') }}
    </div>
    <div
      v-if="ended"
      class="absolute inset-x-0 top-0 bg-red-950/90 px-4 py-2 text-center text-sm text-red-200"
    >
      {{ t('terminal.sessionEnded') }}
    </div>
  </div>
</template>

<script setup lang="ts">
// Terminal xterm branché sur le client WS multiplexé : stdin, resize (seulement
// si controlling), flow control et resync entièrement délégués au ws-client.
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';
import { wsClient, type Attachment } from '../lib/ws-client';

const props = withDefaults(defineProps<{ sessionId: string; mode?: 'interactive' | 'observer' }>(), {
  mode: 'interactive',
});

const { t } = useI18n();
const container = useTemplateRef<HTMLDivElement>('container');
const controlling = ref(true);
const ended = ref(false);

let term: Terminal | null = null;
let attachment: Attachment | null = null;
let resizeObserver: ResizeObserver | null = null;
let disposed = false;

onMounted(async () => {
  if (!container.value) return;

  term = new Terminal({
    cursorBlink: true,
    fontSize: 13,
    fontFamily: "ui-monospace, 'JetBrains Mono', 'Fira Code', Menlo, Consolas, monospace",
    scrollback: 10_000,
    theme: {
      background: '#09090b',
      foreground: '#d4d4d8',
      cursor: '#e4e4e7',
      selectionBackground: '#3f3f46',
    },
  });
  const fit = new FitAddon();
  term.loadAddon(fit);
  term.open(container.value);

  try {
    const { WebglAddon } = await import('@xterm/addon-webgl');
    const webgl = new WebglAddon();
    webgl.onContextLoss(() => webgl.dispose());
    term.loadAddon(webgl);
  } catch {
    // WebGL indisponible : xterm retombe sur le renderer DOM
  }

  fit.fit();
  const activeTerm = term;

  // Re-mesure après stabilisation du layout flex : au `onMounted`, le conteneur peut ne pas avoir sa
  // taille finale (la hauteur flex se résout après le 1er paint) → un seul `fit()` donne trop peu de
  // lignes et déforme le TUI. On refait donc le calcul en `requestAnimationFrame`, puis on resynchronise
  // les dimensions PTY. Sert aussi après un resync (reconnexion) où l'écran est remis à plat.
  const refit = (): void => {
    try {
      fit.fit();
    } catch {
      /* conteneur sans dimension (démontage en cours) : ignoré */
    }
    attachment?.resize(activeTerm.cols, activeTerm.rows);
  };

  try {
    attachment = await wsClient.attach({
      sessionId: props.sessionId,
      mode: props.mode,
      cols: activeTerm.cols,
      rows: activeTerm.rows,
      sink: {
        write: (data, callback) => activeTerm.write(data, callback),
        reset: () => {
          activeTerm.reset();
          requestAnimationFrame(refit);
        },
        onDetached: (reason) => {
          if (reason === 'session_exit' || reason === 'replaced') ended.value = true;
        },
        onControlChanged: (c) => {
          controlling.value = c;
        },
      },
    });
  } catch {
    // session introuvable ou déjà terminée
    ended.value = true;
    controlling.value = false;
    return;
  }
  if (disposed) {
    attachment.detach();
    return;
  }
  controlling.value = attachment.controlling;
  requestAnimationFrame(refit); // recale la taille une fois le layout fullbleed stabilisé

  activeTerm.onData((data) => attachment?.sendStdin(data));
  resizeObserver = new ResizeObserver(refit);
  resizeObserver.observe(container.value);
  if (props.mode === 'interactive') activeTerm.focus();
});

onBeforeUnmount(() => {
  disposed = true;
  resizeObserver?.disconnect();
  attachment?.detach();
  term?.dispose();
});
</script>
