// app.jsx — main App: state, theme, generation loop, glue.

const { useState: aUseState, useEffect: aUseEffect, useRef: aUseRef,
        useMemo: aUseMemo, useReducer: aUseReducer, useCallback: aUseCb } = React;

// ── Tweak defaults (host can rewrite this block) ──────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#0a84ff",
  "palette": ["#0a84ff", "#f5f5f7", "#1d1d1f"],
  "density": "regular",
  "radius": 16,
  "dark": false
}/*EDITMODE-END*/;

const ACCENTS = [
  '#0a84ff', // system blue
  '#ff375f', // system pink
  '#bf5af2', // system purple
  '#30d158', // system green
  '#ff9f0a', // system orange
  '#1d1d1f', // ink (neutral)
];

// ── Initial state ─────────────────────────────────────────────────────────
const INITIAL_STATE = {
  mode: 't2i',
  prompt: '',
  negative: '',
  model: 'aurora-x',
  aspect: '1:1',
  batch: 2,
  seed: null,
  stylePresets: [],
  refImage: null,
  refWeight: 65,
  generating: false,
  progress: 0,
  eta: 0,
  currentRunId: null,
  currentResults: [],
  history: [],
};

function reducer(state, action) {
  switch (action.type) {
    case 'set':
      return { ...state, [action.key]: action.value };
    case 'setMode':
      return { ...state, mode: action.value };
    case 'setRef':
      return { ...state, refImage: action.value };
    case 'togglePreset': {
      const has = state.stylePresets.includes(action.value);
      return {
        ...state,
        stylePresets: has
          ? state.stylePresets.filter((p) => p !== action.value)
          : [...state.stylePresets, action.value],
      };
    }
    case 'startRun': {
      return {
        ...state,
        generating: true,
        progress: 0,
        eta: action.eta,
        currentRunId: action.runId,
        currentResults: [],
      };
    }
    case 'tick':
      return { ...state, progress: action.progress, eta: action.eta };
    case 'finishRun': {
      return {
        ...state,
        generating: false,
        progress: 100,
        eta: 0,
        currentResults: action.results,
        history: [...action.results, ...state.history].slice(0, 80),
      };
    }
    case 'cancel':
      return { ...state, generating: false, progress: 0, eta: 0 };
    case 'favorite': {
      const toggle = (r) => r.id === action.id ? { ...r, favorite: !r.favorite } : r;
      return {
        ...state,
        currentResults: state.currentResults.map(toggle),
        history: state.history.map(toggle),
      };
    }
    case 'clear':
      return { ...state, prompt: '', currentResults: [], refImage: null };
    case 'newJob':
      return { ...state, prompt: '', currentResults: [], refImage: null, negative: '', stylePresets: [] };
    default:
      return state;
  }
}

// ── App ───────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [state, dispatch] = aUseReducer(reducer, INITIAL_STATE);
  const [inspectorOpen, setInspectorOpen] = useLocalState('ai-lab.inspector', true);
  const [historyOpen, setHistoryOpen] = aUseState(false);
  const [detail, setDetail] = aUseState(null);
  const [cmdOpen, setCmdOpen] = aUseState(false);

  // ── OpenRouter integration ──────────────────────────────────────────────
  const [apiKey, setApiKey] = useLocalState('ai-image-lab-key', '');
  const [imageOnly, setImageOnly] = useLocalState('ai-image-lab.imageOnly', true);
  const [liveModels, setLiveModels] = aUseState([]);
  const [modelsLoading, setModelsLoading] = aUseState(false);
  const [modelsError, setModelsError] = aUseState('');
  const [apiError, setApiError] = aUseState('');

  // mirror state.model in a ref so the model-fetch effect can read it
  // without re-firing every time the user changes models.
  const stateModelRef = aUseRef(state.model);
  aUseEffect(() => { stateModelRef.current = state.model; }, [state.model]);

  aUseEffect(() => {
    let cancelled = false;
    setModelsLoading(true);
    setModelsError('');
    const params = new URLSearchParams();
    if (!imageOnly) params.set('imageOnly', 'false');
    const qs = params.toString();
    fetch('/api/models' + (qs ? '?' + qs : ''), {
      headers: apiKey ? { 'X-OpenRouter-Key': apiKey } : {},
    })
      .then(async (r) => {
        const j = await r.json().catch(() => ({}));
        if (!r.ok) throw new Error(j.error || `HTTP ${r.status}`);
        return j;
      })
      .then((j) => {
        if (cancelled) return;
        const arr = (j.models || []).map((m) => ({
          id: m.id,
          name: m.name || m.id,
          tag: m.isFree ? ':free'
             : (m.pricing && (m.pricing.prompt || m.pricing.completion)) ? '$$'
             : '',
          isReal: true,
          realId: m.id,
          modalities: m.modalities,
        }));
        setLiveModels(arr);
        setModelsLoading(false);
        if (arr.length && !arr.find((m) => m.id === stateModelRef.current)) {
          dispatch({ type: 'set', key: 'model', value: arr[0].id });
        }
      })
      .catch((e) => {
        if (cancelled) return;
        setModelsError(e.message || 'Modeller alınamadı');
        setLiveModels([]);
        setModelsLoading(false);
      });
    return () => { cancelled = true; };
  }, [apiKey, imageOnly]);

  // ── theme ───────────────────────────────────────────────────────────────
  aUseEffect(() => {
    document.documentElement.dataset.theme = t.dark ? 'dark' : 'light';
    document.documentElement.style.setProperty('--ai-accent', t.accent);
    document.documentElement.style.setProperty('--ai-radius', t.radius + 'px');
    document.documentElement.dataset.density = t.density;
  }, [t.dark, t.accent, t.radius, t.density]);

  // ── prompt history (localStorage) ───────────────────────────────────────
  const [promptHistory, setPromptHistory] = useLocalState('ai-lab.prompts', []);

  // ── keyboard shortcuts ──────────────────────────────────────────────────
  aUseEffect(() => {
    const onKey = (e) => {
      const isMeta = e.metaKey || e.ctrlKey;
      if (isMeta && e.key.toLowerCase() === 'k') {
        e.preventDefault();
        setCmdOpen((v) => !v);
      } else if (isMeta && /^[1-6]$/.test(e.key)) {
        e.preventDefault();
        const id = MODES[parseInt(e.key, 10) - 1]?.id;
        if (id) dispatch({ type: 'setMode', value: id });
      } else if (isMeta && e.key.toLowerCase() === 'i') {
        e.preventDefault();
        setInspectorOpen((v) => !v);
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // ── generation loop (simulated) ─────────────────────────────────────────
  const runTimers = aUseRef([]);
  const clearTimers = () => {
    runTimers.current.forEach((id) => clearTimeout(id));
    runTimers.current = [];
  };

  aUseEffect(() => () => clearTimers(), []);

  const submit = aUseCb(() => {
    if (state.generating) return;
    setApiError('');
    const p = state.prompt.trim();
    if (!p && state.mode !== 'variations' && state.mode !== 'sketch') return;
    if ((state.mode === 'i2i' || state.mode === 'inpaint' || state.mode === 'outpaint' || state.mode === 'variations') && !state.refImage) return;

    const catalog = liveModels.length ? liveModels : MODELS;
    const selectedLive = liveModels.find((m) => m.id === state.model);
    // OpenRouter chat-completion image gen only fits text→image natively.
    const useRealAPI = state.mode === 't2i' && apiKey && selectedLive;

    const runId = Math.random().toString(36).slice(2);
    const duration = useRealAPI ? 14000 : 2200 + state.batch * 900;
    const eta = Math.ceil(duration / 1000);
    dispatch({ type: 'startRun', runId, eta });

    // remember prompt
    if (p) setPromptHistory((h) => [p, ...h.filter((x) => x !== p)].slice(0, 30));

    // tick (drives progress bar — for real API it's just visual; we snap to
    // 100% when the fetch resolves)
    const t0 = performance.now();
    const tickFn = () => {
      const elapsed = performance.now() - t0;
      const pct = Math.min(useRealAPI ? 92 : 99, (elapsed / duration) * 100);
      const remain = Math.max(0, Math.ceil((duration - elapsed) / 1000));
      dispatch({ type: 'tick', progress: pct, eta: remain });
      if (elapsed < duration) {
        runTimers.current.push(setTimeout(tickFn, 90));
      }
    };
    tickFn();

    // shared result-building helpers
    const baseSeed = state.seed ?? hashStr(p || 'untitled') ^ Math.floor(performance.now());
    const modelObj = catalog.find((m) => m.id === state.model) || catalog[0] || MODELS[0];
    const modeMeta = MODES.find((m) => m.id === state.mode);
    const aspectObj = ASPECTS.find((a) => a.value === state.aspect) || ASPECTS[0];
    const promptWithPresets = state.stylePresets.length
      ? `${p}${state.stylePresets.length ? ', ' + state.stylePresets.join(', ').toLowerCase() : ''}`
      : (p || 'varyasyon');
    const buildResult = (i, imageUrl) => ({
      id: `${runId}-${i}`,
      prompt: promptWithPresets,
      model: state.model,
      modelLabel: modelObj?.name || state.model,
      modeLabel: modeMeta?.label,
      mode: state.mode,
      aspect: state.aspect,
      aspectRatio: aspectObj.w / aspectObj.h,
      seed: (baseSeed + i * 1009) >>> 0,
      t: new Date().toLocaleTimeString('tr', { hour: '2-digit', minute: '2-digit' }),
      favorite: false,
      negative: state.negative || null,
      refWeight: ['i2i', 'inpaint', 'outpaint', 'variations'].includes(state.mode) ? state.refWeight : null,
      imageUrl: imageUrl || null,
      isReal: !!imageUrl,
    });

    if (useRealAPI) {
      const negativeNote = state.negative ? `\n\nKaçınılması gereken: ${state.negative}` : '';
      fetch('/api/generate', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-OpenRouter-Key': apiKey,
        },
        body: JSON.stringify({
          model: selectedLive.realId,
          prompt: promptWithPresets + negativeNote,
        }),
      })
        .then(async (r) => {
          const j = await r.json().catch(() => ({}));
          if (!r.ok) throw new Error(j.error || `HTTP ${r.status}`);
          return j;
        })
        .then((data) => {
          clearTimers();
          const images = (data.images || []).filter(Boolean);
          if (!images.length) {
            throw new Error('Model görsel döndürmedi (modelin görsel modunu desteklediğinden emin ol).');
          }
          const results = images.map((url, i) => buildResult(i, url));
          dispatch({ type: 'finishRun', results });
        })
        .catch((e) => {
          clearTimers();
          setApiError(e.message || 'Üretim başarısız');
          dispatch({ type: 'cancel' });
        });
    } else {
      runTimers.current.push(
        setTimeout(() => {
          const batch = state.mode === 'variations' ? 4 : state.batch;
          const results = Array.from({ length: batch }, (_, i) => buildResult(i, null));
          dispatch({ type: 'finishRun', results });
        }, duration)
      );
    }
  }, [state, setPromptHistory, apiKey, liveModels]);

  // ── derived ─────────────────────────────────────────────────────────────
  const catalog = liveModels.length ? liveModels : MODELS;
  const modelOptions = aUseMemo(
    () => catalog.map((m) => ({ value: m.id, label: m.name, tag: m.tag })),
    [catalog]
  );
  const modelLabel = catalog.find((m) => m.id === state.model)?.name || state.model || 'Model seç';

  // ── handlers ────────────────────────────────────────────────────────────
  const onTryPrompt = (p) => dispatch({ type: 'set', key: 'prompt', value: p });
  const onShufflePrompt = () => {
    const cur = state.prompt.trim();
    let pick = STARTER_PROMPTS[Math.floor(Math.random() * STARTER_PROMPTS.length)];
    if (pick === cur) pick = STARTER_PROMPTS[(STARTER_PROMPTS.indexOf(pick) + 1) % STARTER_PROMPTS.length];
    dispatch({ type: 'set', key: 'prompt', value: pick });
  };
  const onImprove = () => {
    const next = improvePrompt(state.prompt);
    dispatch({ type: 'set', key: 'prompt', value: next });
  };
  const onLoadPrompt = (p) => {
    dispatch({ type: 'set', key: 'prompt', value: p });
    setHistoryOpen(false);
    setDetail(null);
  };
  const onSetAsRef = (r) => {
    dispatch({ type: 'setRef', value: { prompt: r.prompt, seed: r.seed, label: r.prompt.slice(0, 32) + '...' } });
    setHistoryOpen(false);
    setDetail(null);
    if (state.mode === 't2i') dispatch({ type: 'setMode', value: 'i2i' });
  };

  // ── command palette dispatcher ──────────────────────────────────────────
  const onCmd = (cmd) => {
    switch (cmd.type) {
      case 'setMode':   dispatch({ type: 'setMode', value: cmd.value }); break;
      case 'toggleTheme': setTweak('dark', !t.dark); break;
      case 'improvePrompt': onImprove(); break;
      case 'shufflePrompt': onShufflePrompt(); break;
      case 'usePrompt': onLoadPrompt(cmd.value); break;
      case 'openResult': setDetail(cmd.value); break;
    }
  };

  // ── current workspace ───────────────────────────────────────────────────
  const Workspace = WORKSPACES[state.mode] || WORKSPACES.t2i;

  // ── can submit?
  const canSubmit = !state.generating && (
    state.mode === 'sketch'
    || state.mode === 'variations'
    || (state.prompt.trim() && (
      state.mode === 't2i'
      || (state.refImage != null)
    ))
  );

  return (
    <div className={`ai-app density-${t.density}`} data-mode={state.mode}>
      <Sidebar
        mode={state.mode}
        onMode={(v) => dispatch({ type: 'setMode', value: v })}
        theme={t.dark ? 'dark' : 'light'}
        onTheme={(v) => setTweak('dark', v === 'dark')}
        onNew={() => dispatch({ type: 'newJob' })}
      />

      <div className="ai-main">
        <TopBar
          mode={state.mode}
          inspectorOpen={inspectorOpen}
          onToggleInspector={() => setInspectorOpen((v) => !v)}
          onOpenHistory={() => setHistoryOpen((v) => !v)}
        />

        <div className="ai-stage-wrap">
          <div className="ai-stage" data-screen-label={`01 Stage · ${state.mode}`}>
            <Workspace
              state={state}
              dispatch={dispatch}
              currentResults={state.currentResults}
              onTryPrompt={onTryPrompt}
              openDetail={setDetail}
            />
          </div>

          {apiError ? (
            <div style={{
              margin: '0 auto', maxWidth: 720, width: '100%',
              padding: '10px 14px', borderRadius: 12,
              background: 'color-mix(in oklab, #ff375f 18%, transparent)',
              color: 'var(--ai-ink)', border: '1px solid color-mix(in oklab, #ff375f 35%, transparent)',
              display: 'flex', alignItems: 'center', gap: 10, fontSize: 13,
            }}>
              <span style={{ flex: 1 }}>{apiError}</span>
              <button onClick={() => setApiError('')}
                style={{ appearance: 'none', border: 0, background: 'transparent', color: 'inherit',
                  cursor: 'pointer', fontSize: 18, lineHeight: 1, padding: 0, width: 22, height: 22 }}
                aria-label="Kapat">×</button>
            </div>
          ) : null}
          {!apiError && !apiKey && !liveModels.length && !modelsLoading ? (
            <div style={{
              margin: '0 auto', maxWidth: 720, width: '100%',
              padding: '10px 14px', borderRadius: 12,
              background: 'var(--ai-accent-soft)',
              color: 'var(--ai-ink-2)', border: '1px solid var(--ai-line)',
              display: 'flex', alignItems: 'center', gap: 10, fontSize: 13,
            }}>
              <span style={{ flex: 1 }}>
                Gerçek üretim için sağdaki panelden OpenRouter anahtarını gir.
                Şu an mock (demo) modunda — promptlar simüle ediliyor.
              </span>
              <button onClick={() => setInspectorOpen(true)}
                style={{ appearance: 'none', border: 0, background: 'var(--ai-accent)',
                  color: 'var(--ai-accent-ink)', cursor: 'pointer', fontSize: 12, fontWeight: 600,
                  padding: '6px 10px', borderRadius: 8 }}>Aç</button>
            </div>
          ) : null}

          <PromptBar
            prompt={state.prompt}
            onPrompt={(v) => dispatch({ type: 'set', key: 'prompt', value: v })}
            onSubmit={submit}
            onImprove={onImprove}
            onShufflePrompt={onShufflePrompt}
            model={state.model}
            onModel={(v) => dispatch({ type: 'set', key: 'model', value: v })}
            modelOptions={modelOptions}
            generating={state.generating}
            canSubmit={canSubmit}
            mode={state.mode}
          />

          <HistoryStrip
            results={state.history}
            onOpen={setDetail}
            onLoadPrompt={onLoadPrompt}
            onSetAsRef={onSetAsRef}
          />
        </div>
      </div>

      <ControlsPanel
        open={inspectorOpen}
        onClose={() => setInspectorOpen(false)}
        state={state}
        dispatch={dispatch}
        modelLabel={modelLabel}
        apiKey={apiKey}
        onApiKey={setApiKey}
        imageOnly={imageOnly}
        onImageOnly={setImageOnly}
        modelsLoading={modelsLoading}
        modelsError={modelsError}
        liveModelsCount={liveModels.length}
      />

      {/* History overlay */}
      {historyOpen && (
        <div className="ai-modal" onClick={() => setHistoryOpen(false)}>
          <div className="ai-modal-card ai-modal-history" onClick={(e) => e.stopPropagation()}>
            <div className="ai-modal-side-head">
              <div>
                <span className="ai-modal-eyebrow">Tüm geçmiş</span>
                <h2>{state.history.length ? `${state.history.length} görsel` : 'Henüz boş'}</h2>
              </div>
              <IconButton label="Kapat" onClick={() => setHistoryOpen(false)}>
                <IconClose size={16} />
              </IconButton>
            </div>
            {state.history.length ? (
              <div className="ai-history-grid">
                {state.history.map((r) => (
                  <button key={r.id} className="ai-history-tile" onClick={() => setDetail(r)}>
                    {r.imageUrl ? (
                      <img src={r.imageUrl} alt={r.prompt}
                           style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
                    ) : (
                      <GenSVG prompt={r.prompt} seed={r.seed}
                              style={{ width: '100%', height: '100%', display: 'block' }} />
                    )}
                    <div className="ai-history-tile-foot">
                      <span>{r.prompt}</span>
                      <span className="ai-history-tile-meta">{r.modelLabel} · {r.t}</span>
                    </div>
                  </button>
                ))}
              </div>
            ) : (
              <div className="ai-history-empty">
                Bir prompt yaz, ⏎ ile başlat — sonuçlar burada birikecek.
              </div>
            )}
          </div>
        </div>
      )}

      {/* Detail modal */}
      <DetailModal
        result={detail}
        onClose={() => setDetail(null)}
        onFavorite={(id) => dispatch({ type: 'favorite', id })}
        onLoadPrompt={onLoadPrompt}
        onSetAsRef={onSetAsRef}
      />

      {/* Command palette */}
      <CommandPalette
        open={cmdOpen}
        onClose={() => setCmdOpen(false)}
        onCommand={onCmd}
        results={state.history}
      />

      {/* Tweaks panel */}
      <TweaksPanel>
        <TweakSection label="Görünüm" />
        <TweakToggle label="Koyu tema" value={t.dark} onChange={(v) => setTweak('dark', v)} />
        <TweakColor
          label="Vurgu rengi"
          value={t.accent}
          options={ACCENTS}
          onChange={(v) => setTweak('accent', v)}
        />
        <TweakRadio
          label="Yoğunluk"
          value={t.density}
          options={['compact', 'regular', 'comfy']}
          onChange={(v) => setTweak('density', v)}
        />
        <TweakSlider
          label="Köşe yumuşaklığı"
          value={t.radius}
          min={6} max={24} step={1} unit="px"
          onChange={(v) => setTweak('radius', v)}
        />
      </TweaksPanel>
    </div>
  );
}

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