// Micara bridge — UI React (refonte t_fa529748)
// Deux écrans (app silencieuse) : login 1× et statut. 100 % composants
// Astryx (aucun <button>/<input> brut), thème iOS (src/theme.jsx), i18n FR/EN.
// Le moteur WebRTC/mixage vit dans engine.js — ce composant ne fait que
// brancher l'état dessus (adaptateur ui) et afficher.
import { useEffect, useRef, useState } from 'react';
import {
  Badge,
  Button,
  Card,
  Center,
  SegmentedControl,
  SegmentedControlItem,
  Stack,
  StatusDot,
  Text,
  TextInput,
} from '@astryxdesign/core';
import { api as engine } from './engine';
import { t, detectLang } from './i18n';
import logoUrl from './logo.svg';
import './app.css';

// statut WS → variantes Astryx (ancien setDot : connected/connecting/disconnected)
function statusVariant(status) {
  if (status === 'connected') return 'success';
  if (status === 'disconnected') return 'error';
  return 'warning';
}

function statusText(lang, status) {
  if (status === 'connected') return t(lang, 'connected');
  if (status === 'disconnected') return t(lang, 'lost');
  return t(lang, 'connecting');
}

export default function App() {
  const lang = detectLang();
  const [view, setView] = useState('loading'); // loading | login | status
  const [bridgeStatus, setBridgeStatus] = useState('connecting');
  const [spaceCode, setSpaceCode] = useState(null);
  const [sleeping, setSleeping] = useState(false);
  const [updateInfo, setUpdateInfo] = useState(null);
  const [logs, setLogs] = useState([]);
  const [version, setVersion] = useState(null);
  // formulaire de login (épuré — décision 22/08 : logo + email + mot de
  // passe + bouton, AUCUN détail technique : pas d'URL serveur, pas de
  // mention jeton/trousseau. Le serveur est toujours app.getmicara.com.)
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [busy, setBusy] = useState(false);
  const [loginError, setLoginError] = useState(null);
  const [outputMode, setOutputModeState] = useState('blackhole');
  // micro par défaut du système (t_dd15f264) : bannière 1er lancement
  const [micSetup, setMicSetup] = useState(null);
  const [micBusy, setMicBusy] = useState(false);

  const logBoxRef = useRef(null);

  // journal : append (cap 200 lignes) + autoscroll
  function appendLog(line, cls) {
    setLogs((prev) => {
      const next = [...prev, { line, cls }];
      return next.length > 200 ? next.slice(next.length - 200) : next;
    });
  }

  // autoscroll du journal
  useEffect(() => {
    const el = logBoxRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [logs]);

  useEffect(() => {
    // branche le moteur (une seule fois — init est idempotent)
    engine.init({
      onLog: appendLog,
      onStatus: ({ status, spaceCode: sc, sleeping: slp }) => {
        if (status) setBridgeStatus(status);
        if (sc) setSpaceCode(sc);
        if (typeof slp === 'boolean') setSleeping(slp);
      },
      onUpdate: setUpdateInfo,
      onMicSetup: setMicSetup,
      onAuthed: (r) => {
        if (r.ok) setView('status');
        else {
          setLoginError(t(lang, 'sessionExpired'));
          setView('login');
        }
      },
    });

    // état initial (main.js : app:state). Hors Electron (ex. dev navigateur),
    // window.bridge n'existe pas → écran login inerte, pas de crash.
    let cancelled = false;
    if (!window.bridge || typeof window.bridge.state !== 'function') {
      setView('login');
      return () => { cancelled = true; };
    }
    window.bridge.state().then((st) => {
      if (cancelled) return;
      setVersion(st.version || null);
      if (st.bridgeStatus) setBridgeStatus(st.bridgeStatus);
      if (st.spaceCode) setSpaceCode(st.spaceCode);
      if (st.sleeping) setSleeping(true);
      if (st.updateInfo) setUpdateInfo(st.updateInfo);
      if (st.micSetup) setMicSetup(st.micSetup);
      setView(st.authed ? 'status' : 'login');
    });
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  async function submitLogin() {
    setBusy(true);
    setLoginError(null);
    const res = await engine.submitLogin({ email, password });
    setBusy(false);
    if (res.ok) {
      setPassword('');
      setView('status');
    } else {
      setLoginError(t(lang, 'loginError', res.error));
    }
  }

  async function doLogout() {
    await engine.logout();
    setSpaceCode(null);
    setBridgeStatus('connecting');
    setUpdateInfo(null);
    setView('login');
  }

  function changeOutput(mode) {
    setOutputModeState(mode);
    engine.setOutputMode(mode);
  }

  // bannière micro (t_dd15f264) : re-tente le réglage du micro par défaut
  async function applyMic() {
    setMicBusy(true);
    const res = await engine.applyMicSetup();
    setMicBusy(false);
    // le main repousse l'état via onMicSetup (app:mic-setup) → setMicSetup
    if (res && res.ok) setMicSetup({ ...micSetup, ok: true, deviceName: res.deviceName, error: null });
  }

  function dismissMic() {
    engine.dismissMicNotice();
    setMicSetup((s) => (s ? { ...s, dismissed: true } : s));
  }

  const showUpdate = updateInfo && updateInfo.update;
  const statusTxt = statusText(lang, bridgeStatus);
  // raison d'échec du réglage micro → texte lisible (i18n)
  const micReason = micSetup && !micSetup.ok
    ? (micSetup.error === 'missing'
        ? t(lang, 'micSetupReasonMissing')
        : micSetup.error === 'notfound'
          ? t(lang, 'micSetupReasonNotFound')
          : (micSetup.error || t(lang, 'micSetupReasonNotFound')))
    : null;
  const micDevice = (micSetup && micSetup.deviceName) || t(lang, 'outputBlackhole');

  return (
    <Center minHeight="100dvh" padding={5}>
      <Stack gap={5} width="100%" maxWidth={380}>
        {view === 'login' && (
          <Card padding={6}>
            <Stack gap={5} vAlign="center">
              {/* logo — le seul élément de marque du login (décision 22/08 :
                  aucun détail technique, pas d'URL serveur ni de texte jeton) */}
              <img src={logoUrl} width={64} height={64} alt="Micara" />
              <Stack gap={4} width="100%">
                <TextInput
                  label={t(lang, 'email')}
                  type="email"
                  value={email}
                  onChange={setEmail}
                  width="100%"
                />
                <TextInput
                  label={t(lang, 'password')}
                  type="password"
                  value={password}
                  onChange={setPassword}
                  onEnter={submitLogin}
                  width="100%"
                />
                {loginError && <Badge variant="error" label={loginError} />}
                <Button
                  label={t(lang, 'login')}
                  variant="primary"
                  onClick={submitLogin}
                  isLoading={busy}
                  isDisabled={busy}
                  width="100%"
                />
              </Stack>
            </Stack>
          </Card>
        )}

        {view === 'status' && (
          <>
            {/* entête : titre + version */}
            <Stack direction="horizontal" justify="space-between" vAlign="center">
              <Text as="h1" size={20} weight={700}>{t(lang, 'title')}</Text>
              {version && <Badge variant="neutral" label={`v${version}`} />}
            </Stack>
            {/* bannière micro (t_dd15f264) : 1er lancement — micro par défaut + Teams */}
            {micSetup && !micSetup.dismissed && (
              <Card padding={5}>
                <Stack gap={3}>
                  <Text size={14} weight={700}>{t(lang, 'micSetupTitle')}</Text>
                  {micSetup.ok
                    ? <Text size={13}>{t(lang, 'micSetupApplied', micDevice)}</Text>
                    : <Text size={13}>{t(lang, 'micSetupFailed', micReason)}</Text>}
                  <Text size={13} color="muted">{t(lang, 'micSetupTeamsHint', micDevice)}</Text>
                  <Stack direction="horizontal" gap={3} wrap>
                    {!micSetup.ok && (
                      <Button
                        label={t(lang, 'micSetupApply')}
                        variant="ghost"
                        onClick={applyMic}
                        isLoading={micBusy}
                        isDisabled={micBusy}
                      />
                    )}
                    <Button label={t(lang, 'micSetupOk')} variant="primary" onClick={dismissMic} />
                  </Stack>
                </Stack>
              </Card>
            )}

            {/* statut connexion : StatusDot + Badge */}
            <Card padding={6}>
              <Stack gap={4}>
                <Stack direction="horizontal" gap={3} vAlign="center" wrap>
                  <StatusDot variant={statusVariant(bridgeStatus)} label={statusTxt} isPulsing={bridgeStatus === 'connected'} />
                  <Badge variant={statusVariant(bridgeStatus)} label={statusTxt} />
                </Stack>
                {spaceCode && (
                  <Stack gap={1}>
                    <Text size={13} color="muted">{t(lang, 'spaceCode')}</Text>
                    <Text size={20} weight={700} hasTabularNumbers>{spaceCode}</Text>
                  </Stack>
                )}
                {sleeping && bridgeStatus === 'connected' && (
                  <Badge variant="success" label={t(lang, 'sleeping')} />
                )}
                {showUpdate && (
                  updateInfo.downloading
                    ? (
                      <Text size={13} color="muted">
                        {t(lang, 'updateDownloading', updateInfo.latest)} ({updateInfo.percent ?? 0} %)
                      </Text>
                    )
                    : updateInfo.download
                      ? (
                        <Button
                          label={t(lang, 'updateReady', updateInfo.latest)}
                          variant="ghost"
                          onClick={() => engine.installUpdate()}
                          width="100%"
                        />
                      )
                      : <Badge variant="warning" label={t(lang, 'updateError', updateInfo.latest)} />
                )}
              </Stack>
            </Card>

            {/* sortie audio */}
            <Stack gap={2}>
              <Text size={13} color="muted">{t(lang, 'output')}</Text>
              <SegmentedControl
                label={t(lang, 'output')}
                value={outputMode}
                onChange={changeOutput}
              >
                <SegmentedControlItem value="blackhole" label={t(lang, 'outputBlackhole')} />
                <SegmentedControlItem value="speakers" label={t(lang, 'outputSpeakers')} />
              </SegmentedControl>
            </Stack>

            {/* actions */}
            <Stack gap={3}>
              <Button
                label={t(lang, 'dashboard')}
                variant="primary"
                onClick={() => engine.openDashboard()}
                width="100%"
              />
              <Button
                label={t(lang, 'logout')}
                variant="ghost"
                onClick={doLogout}
                width="100%"
              />
            </Stack>

            {/* journal (diagnostic à distance — le bridge est muet) */}
            <Stack gap={2}>
              <Text size={13} color="muted">{t(lang, 'logTitle')}</Text>
              <div className="bridge-log" ref={logBoxRef} aria-label={t(lang, 'logTitle')}>
                {logs.map((l, i) => (
                  <div key={i} className={l.cls || ''}>{l.line}</div>
                ))}
              </div>
            </Stack>
          </>
        )}
      </Stack>
    </Center>
  );
}
