// app.jsx — AutoCut shell: header, screen switcher, Tweaks panel.

const TXT_APP = {
  vi: {
    tweaks: "Tinh chỉnh",
    appearance: "Giao diện",
    theme: "Chủ đề",
    accent: "Màu chủ đạo",
    demo: "Demo",
    replayPipeline: "Chạy lại tiến trình",
    skipToOutput: "Xem kết quả",
    resetToInput: "Quay lại soạn thảo",
    project: "Dự án",
    compose: "Soạn thảo",
    generate: "Khởi tạo",
    deliver: "Hoàn tất",
    history: "Lịch sử",
    templates: "Mẫu",
    light: "Sáng",
    dark: "Tối",
    logout: "Đăng xuất",
    apiError: "Không gọi được /api/generate: ",
  },
  en: {
    tweaks: "Tweaks",
    appearance: "Appearance",
    theme: "Theme",
    accent: "Accent",
    demo: "Demo",
    replayPipeline: "Replay pipeline",
    skipToOutput: "Skip to output",
    resetToInput: "Reset to input",
    project: "Project",
    compose: "Compose",
    generate: "Generate",
    deliver: "Deliver",
    history: "History",
    templates: "Templates",
    light: "Light",
    dark: "Dark",
    logout: "Log out",
    apiError: "Failed to call /api/generate: ",
  }
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "accent": "#FFB547"
}/*EDITMODE-END*/;

const ACCENT_OPTIONS = [
  "#FFB547",  // amber  (default)
  "#19E5BE",  // cyan / mint
  "#FF6584",  // magenta
  "#A8E060",  // lime
  "#7B8BFF",  // violet
];

// Convert FE hex "#RRGGBB" or "#RRGGBBAA" → ASS color "&HAABBGGRR".
// ASS alpha is inverted: 00 = opaque, FF = transparent.
function hexToAssColor(hex) {
  if (!hex || typeof hex !== "string") return null;
  let h = hex.replace("#", "");
  if (h.length === 6) h += "FF";
  if (h.length !== 8) return null;
  const r = h.slice(0, 2).toUpperCase();
  const g = h.slice(2, 4).toUpperCase();
  const b = h.slice(4, 6).toUpperCase();
  const aHex = parseInt(h.slice(6, 8), 16);
  const aAss = (255 - aHex).toString(16).padStart(2, "0").toUpperCase();
  return `&H${aAss}${b}${g}${r}`;
}

// map hex → oklch components so we can recompute soft/line variants
function hexToOklchVars(hex) {
  // simple approach: just use rgba with opacity for soft/line
  const r = parseInt(hex.slice(1,3), 16);
  const g = parseInt(hex.slice(3,5), 16);
  const b = parseInt(hex.slice(5,7), 16);
  // perceived lightness for contrasted fg
  const luminance = (0.299*r + 0.587*g + 0.114*b) / 255;
  return {
    accent: hex,
    soft: `rgba(${r},${g},${b},0.14)`,
    line: `rgba(${r},${g},${b},0.32)`,
    fg: luminance > 0.6 ? "#1a1408" : "#fff8ee",
  };
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [authed, setAuthed] = React.useState(() => isAuthed());
  const [appMode, setAppMode] = React.useState("studio"); // studio (script→video) | enhance (video→video)
  const [screen, setScreen] = React.useState("input"); // input | pipeline | output
  const [job, setJob] = React.useState({ id: null, videoUrl: null, error: null });

  const initialUiLang = localStorage.getItem("autocut.uiLang") || "vi";
  const [uiLang, setUiLang] = React.useState(initialUiLang);
  const appTxt = TXT_APP[uiLang] || TXT_APP.vi;

  const [state, setState] = React.useState(() => {
    let savedCustomPrompt = "";
    let savedCustomStyle = "";
    let savedTemplates = null;
    let savedImageStyle = "";
    try {
      savedCustomPrompt = localStorage.getItem("autocut.customPrompt") || "";
      savedCustomStyle = localStorage.getItem("autocut.customStyle") || "";
      const tpls = localStorage.getItem("autocut.templates");
      savedTemplates = tpls ? JSON.parse(tpls) : null;
      savedImageStyle = localStorage.getItem("autocut.imageStyle") || "";
    } catch (e) {
      console.error("Failed to load saved state", e);
    }

    return {
      source: initialUiLang === "vi" ? SAMPLE_SCRIPT_VI : SAMPLE_SCRIPT,
      customPrompt: savedCustomPrompt,
      customStyle: savedCustomStyle,
      sourceMode: "script",
      uploads: [], // [{file: File, description: string, previewUrl: string}]
      audioFile: null, // audio-driven mode: { file: File, name, duration } — video follows this narration (no TTS)
      settings: {
        language: initialUiLang === "vi" ? "Vietnamese" : "English",
        style: "presentation",
        image_style: savedImageStyle,
        aspect: "16:9",
        resolution: "1080",
        voice: "narrator-warm",
        pacing: 1.0,
        enableSubtitles: false,
        numScenes: 0,
        templates: savedTemplates,
        templatesOnly: true, // strict-mode: every scene MUST use a template (Agent 1 cannot set template=null)
      },
      sub: {
        font: "Arial",
        size: 36,
        maxWords: 6,
        position: "bottom",
        marginV: 120,
        text: "#FFFFFFFF",
        outline: "#000000FF",
        back: "#000000A0",
        highlight: "#FFD60AFF",
        name: "Studio",
      },
    };
  });

  const changeUiLang = React.useCallback((lang) => {
    setUiLang(lang);
    localStorage.setItem("autocut.uiLang", lang);
    setState((prev) => {
      const isSourceDefaultSample = prev.source === SAMPLE_SCRIPT || prev.source === SAMPLE_SCRIPT_VI;
      const nextSource = isSourceDefaultSample
        ? (lang === "vi" ? SAMPLE_SCRIPT_VI : SAMPLE_SCRIPT)
        : prev.source;

      return {
        ...prev,
        source: nextSource,
        settings: {
          ...prev.settings,
          language: lang === "vi" ? "Vietnamese" : "English",
        },
      };
    });
  }, []);

  // Remix → Studio handoff: drop the rewritten script into the input screen.
  const handleSendToStudio = React.useCallback((script, langB) => {
    setState((prev) => ({
      ...prev,
      source: script || prev.source,
      settings: { ...prev.settings, language: langB || prev.settings.language },
    }));
    setAppMode("studio");
    setScreen("input");
  }, []);

  const startJob = React.useCallback(async (shouldSaveTemplate) => {
    setJob({ id: null, videoUrl: null, error: null });
    try {
      if (shouldSaveTemplate) {
        try {
          localStorage.setItem("autocut.customPrompt", state.customPrompt || "");
          localStorage.setItem("autocut.customStyle", state.customStyle || "");
          localStorage.setItem("autocut.imageStyle", state.settings.image_style || "");
          if (state.settings.templates) {
            localStorage.setItem("autocut.templates", JSON.stringify(state.settings.templates));
          } else {
            localStorage.removeItem("autocut.templates");
          }
        } catch (e) {
          console.error("Failed to save state", e);
        }
      }
      // Stage 1: if user uploaded any local assets, send them first and get an upload_id.
      let uploadId = null;
      const uploads = state.uploads || [];
      if (uploads.length > 0) {
        const fd = new FormData();
        uploads.forEach((u) => fd.append("files", u.file));
        fd.append("descriptions", JSON.stringify(uploads.map((u) => u.description || "")));
        const upRes = await fetch("/api/uploads", { method: "POST", body: fd });
        if (!upRes.ok) {
          const errBody = await upRes.text();
          throw new Error(`/api/uploads ${upRes.status}: ${errBody}`);
        }
        const upData = await upRes.json();
        uploadId = upData.upload_id;
      }

      // Stage 1b: audio-driven mode — upload the narration audio and get an audio_upload_id.
      let audioUploadId = null;
      const audioMode = state.sourceMode === "audio" && state.audioFile && state.audioFile.file;
      if (audioMode) {
        const fd = new FormData();
        fd.append("file", state.audioFile.file);
        const aRes = await fetch("/api/uploads/audio", { method: "POST", body: fd });
        if (!aRes.ok) {
          const errBody = await aRes.text();
          throw new Error(`/api/uploads/audio ${aRes.status}: ${errBody}`);
        }
        const aData = await aRes.json();
        audioUploadId = aData.audio_upload_id;
      }

      // Stage 2: kick off generation with the upload_id / audio_upload_id (if any).
      const res = await fetch("/api/generate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          prompt: audioMode ? "" : state.source,
          custom_prompt: state.customPrompt || "",
          style: state.settings.style,
          custom_style: (state.settings.style === "custom") ? (state.customStyle || "") : "",
          image_style: state.settings.image_style || "",
          language: state.settings.language,
          aspect_ratio: state.settings.aspect,
          enable_subtitles: state.settings.enableSubtitles !== false,
          num_scenes: state.settings.numScenes > 0 ? state.settings.numScenes : null,
          // null  → user never touched selection → backend uses style defaults
          // []    → user explicitly unchecked everything → no templates (fully custom Agent 2)
          // [...] → user's explicit subset
          templates: state.settings.templates !== null ? state.settings.templates : null,
          templates_only: state.settings.templatesOnly !== false,
          upload_id: uploadId,
          audio_upload_id: audioUploadId,
        }),
      });
      if (!res.ok) throw new Error(`API ${res.status}`);
      const data = await res.json();
      setJob({ id: data.job_id, videoUrl: null, error: null });
      setScreen("pipeline");
    } catch (e) {
      setJob({ id: null, videoUrl: null, error: String(e.message || e) });
      alert(appTxt.apiError + (e.message || e));
    }
  }, [state.source, state.customPrompt, state.customStyle, state.settings, state.uploads, state.sourceMode, state.audioFile, appTxt.apiError]);

  // apply theme + accent globally
  React.useEffect(() => {
    document.documentElement.dataset.theme = t.theme || "dark";
    const v = hexToOklchVars(t.accent);
    document.documentElement.style.setProperty("--accent", v.accent);
    document.documentElement.style.setProperty("--accent-soft", v.soft);
    document.documentElement.style.setProperty("--accent-line", v.line);
    document.documentElement.style.setProperty("--accent-fg", v.fg);
  }, [t.theme, t.accent]);

  // keyboard: cmd+enter to generate from input screen
  React.useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && screen === "input") {
        const audioReady = state.sourceMode === "audio" && state.audioFile && state.audioFile.file;
        if (audioReady || state.source.trim().split(/\s+/).length >= 20) startJob();
      }
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [screen, state.source, state.sourceMode, state.audioFile, startJob]);

  if (!authed) {
    return <LoginScreen uiLang={uiLang} onAuthenticated={() => setAuthed(true)} />;
  }

  return (
    <div style={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
      <TopBar
        screen={screen}
        onScreenChange={setScreen}
        canPipeline={state.source.trim().split(/\s+/).length >= 20}
        appMode={appMode}
        onAppModeChange={setAppMode}
        theme={t.theme}
        onToggleTheme={() => {
          const isDark = t.theme === "dark";
          setTweak({
            theme: isDark ? "light" : "dark",
            accent: isDark ? "#60A5FA" : "#FFB547"
          });
        }}
        uiLang={uiLang}
        onUiLangChange={changeUiLang}
      />

      <main style={{ flex: 1, paddingTop: 18 }}>
        {appMode === "enhance" && <EnhanceScreen uiLang={uiLang} />}
        {appMode === "remix" && <RemixScreen uiLang={uiLang} onSendToStudio={handleSendToStudio} />}
        {appMode === "studio" && screen === "input" && (
          <InputScreen
            state={state}
            setState={setState}
            onGenerate={startJob}
            uiLang={uiLang}
          />
        )}
        {appMode === "studio" && screen === "pipeline" && (
          <PipelineScreen
            state={state}
            jobId={job.id}
            onComplete={(videoUrl) => { setJob((j) => ({ ...j, videoUrl })); setScreen("output"); }}
            onError={(err) => setJob((j) => ({ ...j, error: err }))}
            uiLang={uiLang}
          />
        )}
        {appMode === "studio" && screen === "output" && (
          <OutputScreen state={state} job={job} onRestart={() => setScreen("input")} onUpdateJob={(j) => setJob(j)} uiLang={uiLang}/>
        )}
      </main>

      <TweaksPanel title={appTxt.tweaks}>
        <TweakSection label={appTxt.appearance}/>
        <TweakRadio label={appTxt.theme} value={t.theme}
          options={["dark", "light"]}
          onChange={(v) => setTweak("theme", v)}/>
        <TweakColor label={appTxt.accent} value={t.accent}
          options={ACCENT_OPTIONS}
          onChange={(v) => setTweak("accent", v)}/>

        <TweakSection label={appTxt.demo}/>
        <TweakButton
          label={appTxt.replayPipeline}
          onClick={() => setScreen("pipeline")}/>
        <TweakButton
          label={appTxt.skipToOutput}
          onClick={() => setScreen("output")}/>
        <TweakButton
          label={appTxt.resetToInput}
          onClick={() => setScreen("input")}/>

        <TweakSection label={appTxt.project}/>
        <TweakButton
          label={appTxt.logout}
          onClick={() => { localStorage.removeItem("autocut.authed"); setAuthed(false); }}/>
      </TweaksPanel>
    </div>
  );
}

function TopBar({ screen, onScreenChange, canPipeline, appMode, onAppModeChange, theme, onToggleTheme, uiLang, onUiLangChange }) {
  const appTxt = TXT_APP[uiLang] || TXT_APP.vi;
  const modeLabels = uiLang === "en"
    ? { studio: "Studio", enhance: "Enhance", remix: "Remix" }
    : { studio: "Studio", enhance: "Làm đẹp", remix: "Viết lại" };
  const steps = uiLang === "en" ? [
    { id: "input", label: "Compose" },
    { id: "pipeline", label: "Generate" },
    { id: "output", label: "Deliver" },
  ] : [
    { id: "input", label: "Soạn thảo" },
    { id: "pipeline", label: "Khởi tạo" },
    { id: "output", label: "Hoàn tất" },
  ];
  const activeIdx = steps.findIndex(s => s.id === screen);

  return (
    <header style={{
      position: "sticky", top: 0, zIndex: 10,
      display: "grid",
      gridTemplateColumns: "1fr auto 1fr",
      alignItems: "center",
      padding: "14px 28px",
      borderBottom: "1px solid var(--line)",
      background: "color-mix(in oklab, var(--bg-0) 80%, transparent)",
      backdropFilter: "blur(12px) saturate(140%)",
      WebkitBackdropFilter: "blur(12px) saturate(140%)",
      marginBottom: 4,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 18 }}>
        <AutoCutLogo size={26}/>
        <div style={{
          padding: "3px 8px",
          background: "var(--bg-2)",
          border: "1px solid var(--line)",
          borderRadius: 4,
          fontFamily: "var(--font-mono)",
          fontSize: 10.5,
          color: "var(--fg-2)",
          letterSpacing: "0.06em",
          textTransform: "uppercase",
        }}>
          {appTxt.project} · voyager_doc
        </div>

        {/* mode toggle: Studio (script→video) vs Enhance (video→video) */}
        <div style={{
          display: "flex", background: "var(--bg-3)", border: "1px solid var(--line)",
          borderRadius: "var(--radius)", padding: 2, gap: 2,
        }}>
          {["studio", "enhance", "remix"].map((m) => (
            <button key={m} onClick={() => onAppModeChange(m)} style={{
              padding: "4px 11px", fontSize: 11.5, fontWeight: appMode === m ? 600 : 500,
              borderRadius: "calc(var(--radius) - 2px)", border: "none",
              background: appMode === m ? "var(--accent)" : "transparent",
              color: appMode === m ? "var(--accent-fg)" : "var(--fg-2)",
              cursor: "pointer", transition: "all .12s",
            }}>{modeLabels[m]}</button>
          ))}
        </div>
      </div>

      {/* center stepper — only for studio (script→video) flow */}
      <nav style={{ display: appMode === "studio" ? "flex" : "none", alignItems: "center", gap: 4 }}>
        {steps.map((s, i) => {
          const isActive = i === activeIdx;
          const isPast = i < activeIdx;
          const clickable = i <= activeIdx || (i === 1 && canPipeline);
          return (
            <React.Fragment key={s.id}>
              <button
                onClick={() => clickable && onScreenChange(s.id)}
                disabled={!clickable}
                style={{
                  display: "flex", alignItems: "center", gap: 8,
                  padding: "7px 12px",
                  borderRadius: "var(--radius)",
                  fontSize: 12.5,
                  fontWeight: isActive ? 600 : 500,
                  color: isActive ? "var(--fg-0)" : isPast ? "var(--fg-1)" : "var(--fg-3)",
                  background: isActive ? "var(--bg-3)" : "transparent",
                  cursor: clickable ? "pointer" : "not-allowed",
                  transition: "background .12s",
                }}
              >
                <span style={{
                  width: 18, height: 18, borderRadius: 999,
                  display: "flex", alignItems: "center", justifyContent: "center",
                  fontSize: 10, fontWeight: 600,
                  background: isActive ? "var(--accent)" : isPast ? "var(--ok)" : "var(--bg-3)",
                  color: isActive || isPast ? "var(--accent-fg)" : "var(--fg-3)",
                  border: isActive ? "none" : isPast ? "none" : "1px solid var(--line)",
                }} className="mono">
                  {isPast ? "✓" : i + 1}
                </span>
                {s.label}
              </button>
              {i < steps.length - 1 && (
                <div style={{ width: 18, height: 1, background: "var(--line-strong)" }}/>
              )}
            </React.Fragment>
          );
        })}
      </nav>

      <div style={{ display: "flex", justifyContent: "flex-end", alignItems: "center", gap: 10 }}>
        {/* Language Switcher Toggle */}
        <div style={{
          display: "flex",
          background: "var(--bg-3)",
          border: "1px solid var(--line)",
          borderRadius: "var(--radius)",
          padding: 2,
          gap: 2,
        }}>
          <button
            onClick={() => onUiLangChange("vi")}
            style={{
              padding: "4px 8px",
              fontSize: 11.5,
              fontWeight: uiLang === "vi" ? 600 : 500,
              borderRadius: "calc(var(--radius) - 2px)",
              border: "none",
              background: uiLang === "vi" ? "var(--accent)" : "transparent",
              color: uiLang === "vi" ? "var(--accent-fg)" : "var(--fg-2)",
              cursor: "pointer",
              transition: "all .12s",
            }}
          >
            VI
          </button>
          <button
            onClick={() => onUiLangChange("en")}
            style={{
              padding: "4px 8px",
              fontSize: 11.5,
              fontWeight: uiLang === "en" ? 600 : 500,
              borderRadius: "calc(var(--radius) - 2px)",
              border: "none",
              background: uiLang === "en" ? "var(--accent)" : "transparent",
              color: uiLang === "en" ? "var(--accent-fg)" : "var(--fg-2)",
              cursor: "pointer",
              transition: "all .12s",
            }}
          >
            EN
          </button>
        </div>

        <button onClick={onToggleTheme} style={{
          padding: "7px 10px",
          fontSize: 12.5, color: "var(--fg-1)",
          borderRadius: "var(--radius)",
          display: "flex", alignItems: "center", gap: 6,
          cursor: "pointer",
          border: "none",
          background: "transparent"
        }}
        onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-3)"}
        onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
          {theme === "dark" ? (
            <svg width="14" height="14" viewBox="0 0 16 16"><path d="M8 12 A4 4 0 1 0 8 4 A4 4 0 0 0 8 12 Z M8 1 V3 M8 13 V15 M1 8 H3 M13 8 H15 M3 3 L4.5 4.5 M11.5 11.5 L13 13 M3 13 L4.5 11.5 M11.5 4.5 L13 3" stroke="currentColor" strokeWidth="1.2" fill="none"/></svg>
          ) : (
            <svg width="14" height="14" viewBox="0 0 16 16"><path d="M14 10 A6 6 0 1 1 6 2 A6.5 6.5 0 0 0 14 10 Z" stroke="currentColor" strokeWidth="1.2" fill="none"/></svg>
          )}
          {theme === "dark" ? appTxt.light : appTxt.dark}
        </button>
        <button style={{
          padding: "7px 10px",
          fontSize: 12.5, color: "var(--fg-1)",
          borderRadius: "var(--radius)",
          display: "flex", alignItems: "center", gap: 6,
        }}>
          <svg width="14" height="14" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" strokeWidth="1.4"/><path d="M8 5 V8 L10 9.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" fill="none"/></svg>
          {appTxt.history}
        </button>
        <button style={{
          padding: "7px 10px",
          fontSize: 12.5, color: "var(--fg-1)",
          borderRadius: "var(--radius)",
          display: "flex", alignItems: "center", gap: 6,
        }}>
          <svg width="14" height="14" viewBox="0 0 16 16"><path d="M8 1 L10 5.5 L15 6 L11.5 9.3 L12.5 14 L8 11.6 L3.5 14 L4.5 9.3 L1 6 L6 5.5 Z" fill="none" stroke="currentColor" strokeWidth="1.2"/></svg>
          {appTxt.templates}
        </button>
        <div style={{ height: 18, width: 1, background: "var(--line)", margin: "0 4px" }}/>
        <div style={{
          display: "flex", alignItems: "center", gap: 8,
          padding: "5px 5px 5px 10px",
          background: "var(--bg-2)",
          border: "1px solid var(--line)",
          borderRadius: 999,
          fontSize: 12,
          color: "var(--fg-1)",
        }}>
          <span className="mono" style={{ color: "var(--fg-3)", fontSize: 11 }}>3.2 / 5 hr</span>
          <div style={{
            width: 24, height: 24, borderRadius: 999,
            background: "linear-gradient(135deg, var(--accent), oklch(0.55 0.15 320))",
            color: "var(--accent-fg)",
            display: "flex", alignItems: "center", justifyContent: "center",
            fontSize: 11, fontWeight: 600,
          }}>K</div>
        </div>
      </div>
    </header>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
