// Project R — Edit / Create skill wizard
// Multi-step wizard: 1) Basics, 2) Inputs, 3) API tools, 4) File & save.
// Each step gets the full drawer width so things don't feel cramped.

const { useState: useDS, useEffect: useDE, useRef: useDR } = React;

const STEPS = [
  { key: 'basics', label: 'Basics', subtitle: 'Name and description' },
  { key: 'inputs', label: 'Inputs', subtitle: 'What the LLM needs at runtime' },
  { key: 'tools',  label: 'API tools', subtitle: 'Optional HTTP calls' },
  { key: 'file',   label: 'File & save', subtitle: 'Upload and review' },
];

function Drawer({ open, onClose, skill, onSave, onDelete, drawerWidth = 560, ownerId, draftMode = false }) {
  const isNew = !skill;
  const [step, setStep] = useDS(0);
  const [name, setName] = useDS('');
  const [desc, setDesc] = useDS('');
  const [inputs, setInputs] = useDS([]);
  const [newInputName, setNewInputName] = useDS('');
  const [newInputDesc, setNewInputDesc] = useDS('');
  // api_config is the schema-shaped runtime endpoint list that the
  // ApiConfigSection renders + edits. When uploading a .skill bundle, we
  // populate it from the parsed frontmatter; when editing an existing
  // skill, we hydrate it from skill.api_config.
  const [apiConfig, setApiConfig] = useDS([]);
  const [file, setFile] = useDS(null);
  const [pendingFile, setPendingFile] = useDS(null);
  const [drag, setDrag] = useDS(false);
  const [savedFlash, setFlash] = useDS(false);
  const [saving, setSaving] = useDS(false);
  const [saveError, setSaveError] = useDS('');
  // `used_by` is populated when editing an existing skill: the list of
  // employees whose owned_skills carries this skill_id. Persona is
  // per-employee at run time, so this is purely a "who shares the
  // SKILL contents" view — useful before editing tools / api_config /
  // .skill bundle, since those changes propagate to every owner.
  const [usedBy, setUsedBy] = useDS([]);
  const fileRef = useDR();
  // Upload-and-autofill: send a .skill bundle to /api/skills/parse, then
  // hydrate name + description from the SKILL.md frontmatter and stash the
  // File on `pendingFile` so the final POST sends it with the create. Only
  // surfaced in create-from-scratch mode — editing existing skills keeps
  // the manual flow.
  const skillFileAutofillRef = useDR(null);
  const [parsing, setParsing] = useDS(false);
  // Tracks which fields the backend's LLM extractor (Portkey → Sonnet)
  // filled in vs. which came from SKILL.md frontmatter or user typing.
  // Shape: { name?: true, description?: true, required_inputs?: true,
  // api_config?: true }. A flag clears as soon as the user edits its
  // field — once the user has touched a value, it's no longer "AI-
  // suggested." Cleared on every drawer open so editing an existing
  // skill never shows a stale badge.
  const [aiSuggested, setAiSuggested] = useDS({});
  const clearAi = (k) => setAiSuggested(s => (s && s[k]) ? { ...s, [k]: false } : s);

  const autofillFromSkillFile = async (fs) => {
    if (!fs || !fs[0]) return;
    const f = fs[0];
    if (!/\.skill$/i.test(f.name)) {
      setSaveError('Pick a file ending in .skill.');
      return;
    }
    setSaveError('');
    setParsing(true);
    try {
      const fd = new FormData();
      fd.append('file', f, f.name);
      const token = (() => { try { return localStorage.getItem('pr.token'); } catch { return null; } })();
      const r = await fetch('/api/skills/parse', {
        method: 'POST',
        headers: token ? { Authorization: `Bearer ${token}` } : {},
        body: fd,
      });
      const d = await r.json().catch(() => ({}));
      if (!r.ok) {
        setSaveError(`Couldn't read ${f.name}: ${d.error || 'parse failed'}`);
        return;
      }
      // Apply every field from the parse response in one batch. The
      // backend already ran the deterministic parser AND any LLM
      // fallback for missing fields, so `d` is the final picture —
      // the wizard never shows partially-filled state. ai_suggested
      // marks which fields the LLM filled so the form can render an
      // "AI-suggested" badge for the user to verify before save.
      if (d.name) setName(d.name);
      if (d.description) setDesc(d.description);
      if (Array.isArray(d.required_inputs) && d.required_inputs.length) {
        setInputs(d.required_inputs.map(i => ({ name: i.name, description: i.description })));
      }
      if (Array.isArray(d.api_config)) setApiConfig(d.api_config);
      setAiSuggested(d.ai_suggested && typeof d.ai_suggested === 'object' ? d.ai_suggested : {});
      setPendingFile(f);
      setFile({ name: f.name, size: f.size, version: 'pending', updated: new Date().toISOString().slice(0, 10) });
    } catch (e) {
      setSaveError(String(e?.message || e));
    } finally {
      setParsing(false);
      if (skillFileAutofillRef.current) skillFileAutofillRef.current.value = '';
    }
  };

  useDE(() => {
    if (!open) return;
    setSaveError(''); setPendingFile(null); setStep(0); setAiSuggested({});
    if (skill) {
      setName(skill.name); setDesc(skill.desc);
      setInputs((skill.required_inputs || skill.inputs || []).map(i => ({ ...i })));
      // Bootstrap returns api_config with header VALUES redacted (so a
      // 'user' role browser tab never sees raw Authorization tokens).
      // Admins editing a skill need the real headers to round-trip
      // them — fetch the full record from the admin-only detail
      // endpoint and replace state when it arrives. Until it lands we
      // use the redacted projection so the UI isn't blank.
      setApiConfig(Array.isArray(skill.api_config) ? skill.api_config.map(t => ({ ...t })) : []);
      setFile(skill.file);
      setNewInputName(''); setNewInputDesc('');
      let cancelled = false;
      (async () => {
        try {
          const tok = (() => { try { return localStorage.getItem('pr.token'); } catch { return null; } })();
          const r = await fetch(`/api/skills/${encodeURIComponent(skill.id)}`, {
            headers: tok ? { Authorization: `Bearer ${tok}` } : {},
          });
          if (!r.ok) return;
          const data = await r.json();
          if (cancelled) return;
          const full = data && data.skill;
          if (full && Array.isArray(full.api_config)) {
            setApiConfig(full.api_config.map(t => ({ ...t })));
          }
          if (Array.isArray(data && data.used_by)) {
            setUsedBy(data.used_by);
          }
        } catch (_) { /* keep redacted view; save still safe via server merge */ }
      })();
      return () => { cancelled = true; };
    } else {
      setName(''); setDesc(''); setInputs([]); setApiConfig([]); setFile(null);
      setNewInputName(''); setNewInputDesc(''); setUsedBy([]);
    }
  }, [open, skill && skill.id]);

  if (!open) return null;

  const addInput = () => {
    const n = newInputName.trim();
    const d = newInputDesc.trim();
    if (!n || !d || inputs.some(i => i.name === n)) return;
    setInputs([...inputs, { name: n, description: d }]);
    setNewInputName(''); setNewInputDesc('');
    clearAi('required_inputs');
  };
  const removeInput = (idx) => { setInputs(inputs.filter((_, i) => i !== idx)); clearAi('required_inputs'); };
  const updateInput = (idx, patch) => { setInputs(inputs.map((it, i) => i === idx ? { ...it, ...patch } : it)); clearAi('required_inputs'); };
  const setApiConfigEdited = (next) => { setApiConfig(typeof next === 'function' ? next(apiConfig) : next); clearAi('api_config'); };

  // Attach a .skill file. Beyond stashing it for upload, we *also* run it
  // through /api/skills/parse so api_config (runtime endpoint specs) is
  // captured no matter which entry point the user used — the autofill
  // button at step 0 OR the dropzone at step 4. Manually-typed name/desc
  // are preserved; we only fill blanks.
  const handleFiles = async (fs) => {
    if (!fs || !fs[0]) return;
    const f = fs[0];
    setPendingFile(f);
    setFile({ name: f.name, size: f.size, version: 'pending', updated: new Date().toISOString().slice(0, 10) });
    if (!/\.skill$/i.test(f.name)) return;
    setParsing(true);
    try {
      const fd = new FormData();
      fd.append('file', f, f.name);
      const token = (() => { try { return localStorage.getItem('pr.token'); } catch { return null; } })();
      const r = await fetch('/api/skills/parse', {
        method: 'POST',
        headers: token ? { Authorization: `Bearer ${token}` } : {},
        body: fd,
      });
      if (!r.ok) return;
      const d = await r.json().catch(() => ({}));
      // Always capture the runtime spec — populate the api_config editor.
      if (Array.isArray(d.api_config)) setApiConfig(d.api_config);
      // Fill blanks only — don't clobber a manually-typed name/desc/inputs.
      // For ai_suggested we only mark fields we actually applied here, so
      // a name the user already typed never gets badged "AI-suggested".
      const srcAi = (d && d.ai_suggested && typeof d.ai_suggested === 'object') ? d.ai_suggested : {};
      const appliedAi = {};
      if (d.name && !name.trim()) { setName(d.name); if (srcAi.name) appliedAi.name = true; }
      if (d.description && !desc.trim()) { setDesc(d.description); if (srcAi.description) appliedAi.description = true; }
      if (Array.isArray(d.required_inputs) && d.required_inputs.length && !inputs.length) {
        setInputs(d.required_inputs.map(i => ({ name: i.name, description: i.description })));
        if (srcAi.required_inputs) appliedAi.required_inputs = true;
      }
      // api_config is always replaced above; mirror that in the badge.
      if (Array.isArray(d.api_config) && d.api_config.length && srcAi.api_config) appliedAi.api_config = true;
      setAiSuggested(s => ({ ...(s || {}), ...appliedAi }));
    } catch (_) { /* parse is best-effort here; manual entry still works */ }
    finally { setParsing(false); }
  };

  // Per-step gates so Next is only enabled when the step is valid.
  // In draftMode the skill file is optional — drafts get attached after
  // the parent (e.g. AddEmployeeModal) actually creates the skill.
  const stepValid = [
    !!(name.trim() && desc.trim()),
    true,
    true,
    draftMode ? true : !!file,
  ];
  const canSave = name.trim() && desc.trim() && (draftMode || file) && !saving && !parsing;

  const save = async () => {
    if (!canSave) return;
    setSaveError('');

    // Draft mode: hand the form data back to the host instead of POSTing.
    // The host queues drafts and creates them after the owning record exists.
    if (draftMode) {
      onSave({
        _draft: {
          name: name.trim(), desc: desc.trim(),
          required_inputs: inputs,
          api_config: apiConfig,
          file: pendingFile || null,
        },
        name: name.trim(), desc: desc.trim(), inputs, api_config: apiConfig, file,
      });
      setFlash(true); setTimeout(() => setFlash(false), 1400);
      return;
    }

    if (isNew) {
      if (!ownerId) { setSaveError('Owner missing — open the drawer from an employee\'s skills page.'); return; }
      setSaving(true);
      try {
        const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
        const skillId = `custom.${slug || `skill-${Date.now()}`}`;
        const fd = new FormData();
        fd.append('skill_id', skillId);
        fd.append('name', name.trim());
        fd.append('desc', desc.trim());
        fd.append('owner_id', ownerId);
        fd.append('required_inputs', JSON.stringify(inputs));
        // Always send the api_config array (may be empty). Drives runtime
        // endpoints — schema/skills.json is the source of truth for shape.
        fd.append('api_config', JSON.stringify(apiConfig || []));
        if (pendingFile) fd.append('file', pendingFile, pendingFile.name);
        const token = (() => { try { return localStorage.getItem('pr.token'); } catch { return null; } })();
        const r = await fetch('/api/skills', { method: 'POST', headers: token ? { Authorization: `Bearer ${token}` } : {}, body: fd });
        const data = await r.json().catch(() => ({}));
        if (!r.ok) {
          const base = data.error === 'skill_id_already_exists'
            ? 'A skill with that name already exists.'
            : (data.error || `Create failed (${r.status})`);
          setSaveError(data.detail ? `${base} — ${data.detail}` : base);
          return;
        }
        onSave({ _server: { saved: true, created: true, skill_id: skillId, skill: data.skill }, name: name.trim(), desc: desc.trim(), inputs, api_config: apiConfig, file });
        setPendingFile(null); setFlash(true); setTimeout(() => setFlash(false), 1400);
      } catch (e) { setSaveError(String(e?.message || e)); }
      finally { setSaving(false); }
      return;
    }
    setSaving(true);
    try {
      const fd = new FormData();
      fd.append('name', name.trim());
      fd.append('desc', desc.trim());
      fd.append('required_inputs', JSON.stringify(inputs));
      fd.append('api_config', JSON.stringify(apiConfig || []));
      if (pendingFile) fd.append('file', pendingFile, pendingFile.name);
      const token = (() => { try { return localStorage.getItem('pr.token'); } catch { return null; } })();
      const r = await fetch(`/api/skills/${encodeURIComponent(skill.id)}`, { method: 'PUT', headers: token ? { Authorization: `Bearer ${token}` } : {}, body: fd });
      const data = await r.json().catch(() => ({}));
      if (!r.ok) {
        const base = data.error || `Save failed (${r.status})`;
        setSaveError(data.detail ? `${base} — ${data.detail}` : base);
        return;
      }
      onSave({ name: name.trim(), desc: desc.trim(), inputs, api_config: apiConfig, file, _server: { saved: true, version: data.version || null } });
      setPendingFile(null); setFlash(true); setTimeout(() => setFlash(false), 1400);
    } catch (e) { setSaveError(String(e?.message || e)); }
    finally { setSaving(false); }
  };

  const isLast = step === STEPS.length - 1;
  const goNext = () => { if (isLast) save(); else if (stepValid[step]) setStep(step + 1); };
  const goBack = () => step > 0 && setStep(step - 1);

  // When draftMode is true the Drawer is mounted INSIDE another modal
  // (e.g. AddEmployeeModal), so we drop the viewport-level overlay and
  // anchor with `absolute` so it fills the host card instead of the page.
  const isContained = draftMode;
  return (
    <>
      <div onClick={onClose} style={{
        position: isContained ? 'absolute' : 'fixed', inset: 0,
        background: isContained ? 'rgba(23,53,64,0.18)' : 'rgba(23,53,64,0.32)',
        zIndex: isContained ? 10 : 90, animation: 'pr-fade .15s ease-out',
        borderRadius: isContained ? 'inherit' : 0,
      }} />
      <aside style={{
        position: isContained ? 'absolute' : 'fixed',
        top: 0, right: 0, bottom: 0,
        width: isContained ? '100%' : drawerWidth,
        maxWidth: isContained ? '100%' : '96vw',
        background: 'var(--c-cream-50)',
        borderLeft: isContained ? 'none' : '1px solid var(--border-subtle)',
        boxShadow: isContained ? 'none' : 'var(--shadow-float-lg)',
        zIndex: isContained ? 11 : 91,
        borderRadius: isContained ? 'inherit' : 0,
        display: 'flex', flexDirection: 'column',
        animation: isContained ? 'pr-fade .18s ease-out' : 'pr-slide .22s ease-out',
      }}>
        {/* Header */}
        <header style={{ padding: '20px 24px 14px', borderBottom: '1px solid var(--border-subtle)', background: 'var(--c-cream-100)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div className="eyebrow" style={{ color: 'var(--c-ink-500)', letterSpacing: '1.5px' }}>
                {draftMode ? 'New skill (draft)' : (isNew ? 'New skill' : 'Edit skill')} · Step {step + 1} of {STEPS.length}
              </div>
              <div style={{ fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 700, fontSize: 22, letterSpacing: '-0.02em', color: 'var(--c-ink-800)', marginTop: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {STEPS[step].label}
              </div>
              <div style={{ fontSize: 12, color: 'var(--c-ink-500)', marginTop: 2 }}>{STEPS[step].subtitle}</div>
            </div>
            <button onClick={onClose} title="Close" style={{ width: 36, height: 36, borderRadius: 10, border: '1px solid var(--border-subtle)', background: 'var(--c-white)', boxShadow: 'var(--shadow-raised-sm)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <img src="../assets/icons/cross.svg" width={12} height={12} alt="Close" />
            </button>
          </div>
          {/* Stepper */}
          <div style={{ display: 'flex', gap: 6, marginTop: 14 }}>
            {STEPS.map((s, i) => {
              const done = i < step;
              const active = i === step;
              const reachable = i <= step || (isNew ? false : true);
              return (
                <button key={s.key}
                  onClick={() => reachable && setStep(i)}
                  title={s.label}
                  style={{
                    flex: 1, height: 6, borderRadius: 3, border: 'none', padding: 0,
                    background: done || active ? 'var(--c-petrol-800)' : 'var(--c-cream-50)',
                    boxShadow: active ? '0 0 0 2px var(--c-petrol-050, rgba(23,53,64,0.12))' : 'none',
                    cursor: reachable ? 'pointer' : 'not-allowed', opacity: reachable ? 1 : 0.5,
                    transition: 'background .15s ease-out',
                  }} />
              );
            })}
          </div>
        </header>

        {/* Body */}
        <div style={{ flex: 1, overflow: 'auto', padding: '24px 24px 28px', display: 'flex', flexDirection: 'column', gap: 22 }}>
          {parsing && <ParseLoader />}
          {!parsing && step === 0 && (
            <>
              {!isNew && (
                <div style={{
                  padding: 12, borderRadius: 12,
                  background: 'var(--c-cream-100)', border: '1px solid var(--border-subtle)',
                }}>
                  <div style={{
                    fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 600, fontSize: 12,
                    color: 'var(--c-ink-700)', textTransform: 'uppercase', letterSpacing: '1.2px',
                  }}>
                    Used by {usedBy.length === 0 ? 'no employees' : `${usedBy.length} employee${usedBy.length === 1 ? '' : 's'}`}
                  </div>
                  {usedBy.length > 0 && (
                    <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                      {usedBy.map(e => (
                        <span key={e.id} title={e.role || ''}
                          style={{
                            display: 'inline-flex', alignItems: 'center', gap: 6,
                            padding: '4px 10px', borderRadius: 999,
                            background: 'var(--c-cream-50)', border: '1px solid var(--border-subtle)',
                            fontFamily: "'Suisse Int\\'l', sans-serif", fontSize: 12,
                            color: 'var(--c-ink-900)',
                          }}>
                          <span>{e.name || e.id}</span>
                          {e.role && (
                            <span style={{ fontSize: 10, color: 'var(--c-ink-500)' }}>{e.role}</span>
                          )}
                        </span>
                      ))}
                    </div>
                  )}
                  <div style={{ marginTop: 8, fontSize: 11, color: 'var(--c-ink-500)', lineHeight: 1.45 }}>
                    Skills are shared. Edits to tools / api_config / the .skill bundle propagate
                    to every employee above on their next dispatch. Persona resolves per-employee
                    at run time from <code>employees.soul_md</code>, so each owner keeps their own voice.
                  </div>
                </div>
              )}
              {isNew && !draftMode && (
                <div style={{
                  padding: 14, borderRadius: 12,
                  background: 'var(--c-cream-100)', border: '1px dashed var(--border-default)',
                  display: 'flex', alignItems: 'center', gap: 12,
                }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 600, fontSize: 13,
                      color: 'var(--c-ink-900)',
                    }}>Have a .skill bundle?</div>
                    <div style={{ fontSize: 12, color: 'var(--c-ink-600)', marginTop: 2, lineHeight: 1.45 }}>
                      Upload it and I'll fill in name and description from <code>SKILL.md</code>.
                      Required inputs and API connections aren't read from the bundle —
                      add them in the next steps, or save and edit later.
                    </div>
                  </div>
                  <input ref={skillFileAutofillRef} type="file" accept=".skill"
                    style={{ display: 'none' }}
                    onChange={(ev) => autofillFromSkillFile(ev.target.files)} />
                  <button type="button"
                    onClick={() => skillFileAutofillRef.current && skillFileAutofillRef.current.click()}
                    disabled={parsing}
                    style={{
                      height: 36, padding: '0 14px', borderRadius: 10, border: 'none',
                      background: 'var(--c-petrol-800)', color: '#fff',
                      fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 12,
                      cursor: parsing ? 'wait' : 'pointer', flex: 'none',
                      opacity: parsing ? 0.7 : 1, whiteSpace: 'nowrap',
                    }}>
                    {parsing ? 'Reading…' : 'Upload .skill'}
                  </button>
                </div>
              )}
              <Field label="Display name" ai={!!aiSuggested.name} hint="What learners and admins will see in lists.">
                <input value={name} onChange={e => { setName(e.target.value); clearAi('name'); }} placeholder="e.g. Empathic listening" style={fieldInputStyle} />
              </Field>
              <Field label="Description" ai={!!aiSuggested.description} hint="One paragraph. What it does, who it's for, what to expect.">
                <textarea value={desc} onChange={e => { setDesc(e.target.value); clearAi('description'); }} placeholder="Coach a manager through reflecting back what they hear before responding..."
                  rows={6}
                  style={{ ...fieldInputStyle, height: 'auto', minHeight: 140, resize: 'vertical', padding: '12px 14px', lineHeight: 1.5 }} />
              </Field>
            </>
          )}

          {!parsing && step === 1 && (
            <Field label="Required inputs" ai={!!aiSuggested.required_inputs} hint="Each input has a name and a description. The description guides the LLM at run time, so be specific about format and intent.">
              {inputs.length > 0 && (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 12 }}>
                  {inputs.map((inp, i) => (
                    <InputRow key={i} value={inp} onChange={p => updateInput(i, p)} onRemove={() => removeInput(i)} />
                  ))}
                </div>
              )}
              <div style={{ padding: 14, borderRadius: 12, background: 'var(--c-cream-100)', border: '1px dashed var(--border-default)', display: 'flex', flexDirection: 'column', gap: 10 }}>
                <input value={newInputName}
                  onChange={e => setNewInputName(e.target.value.replace(/\s+/g, '_').toLowerCase())}
                  onKeyDown={e => { if (e.key === 'Enter' && newInputName.trim() && newInputDesc.trim()) { e.preventDefault(); addInput(); } }}
                  placeholder="input_name"
                  style={{ ...fieldInputStyle, height: 40, fontFamily: 'ui-monospace, "JetBrains Mono", Menlo, monospace', fontSize: 13 }} />
                <textarea value={newInputDesc} onChange={e => setNewInputDesc(e.target.value)}
                  placeholder="Describe what this input is and how it should be formatted. The LLM reads this verbatim."
                  rows={3}
                  style={{ ...fieldInputStyle, height: 'auto', minHeight: 76, padding: '10px 14px', resize: 'vertical', lineHeight: 1.45 }} />
                <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                  <button onClick={addInput} disabled={!newInputName.trim() || !newInputDesc.trim()} style={{
                    height: 36, padding: '0 14px', borderRadius: 10, border: 'none',
                    fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 12,
                    background: (newInputName.trim() && newInputDesc.trim()) ? 'var(--c-petrol-800)' : 'var(--c-white)',
                    color: (newInputName.trim() && newInputDesc.trim()) ? '#fff' : 'var(--c-ink-300)',
                    boxShadow: (newInputName.trim() && newInputDesc.trim()) ? 'var(--shadow-raised-sm)' : 'none',
                    cursor: (newInputName.trim() && newInputDesc.trim()) ? 'pointer' : 'not-allowed',
                    display: 'inline-flex', alignItems: 'center', gap: 6,
                  }}>
                    <img src="../assets/icons/plus.svg" width={11} height={11} alt="" style={{ filter: (newInputName.trim() && newInputDesc.trim()) ? 'invert(1) brightness(2)' : 'none' }} />
                    Add input
                  </button>
                </div>
              </div>
            </Field>
          )}

          {!parsing && step === 2 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {aiSuggested.api_config && (
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{ fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 500, fontSize: 14, color: 'var(--c-ink-900)' }}>API tools</span>
                  <AiBadge />
                </div>
              )}
              <ApiConfigSection value={apiConfig} onChange={setApiConfigEdited} />
            </div>
          )}

          {!parsing && step === 3 && (
            <>
              <Field label="Skill file" hint={draftMode ? 'Optional in draft mode — you can attach the .skill bundle now or later.' : 'Drop a .skill bundle, or browse.'}>
                {file ? (
                  <FilePreview file={file} onReplace={() => fileRef.current && fileRef.current.click()} onClear={isNew ? () => setFile(null) : null} isNew={isNew} />
                ) : (
                  <DropZone drag={drag} setDrag={setDrag} onFiles={handleFiles} onBrowse={() => fileRef.current && fileRef.current.click()} />
                )}
                <input ref={fileRef} type="file" style={{ display: 'none' }} onChange={e => handleFiles(e.target.files)} />
              </Field>
              <ReviewSummary name={name} desc={desc} inputs={inputs} apiConfig={apiConfig} file={file} onJump={setStep} />
            </>
          )}
        </div>

        {/* Footer */}
        <footer style={{ position: 'relative', padding: '16px 24px', borderTop: '1px solid var(--border-subtle)', background: 'var(--c-cream-100)', display: 'flex', alignItems: 'center', gap: 10 }}>
          {!isNew && onDelete && step === 0 && (
            <button onClick={() => onDelete(skill)} style={{ height: 44, padding: '0 16px', borderRadius: 12, border: '1.5px solid var(--c-terra-600)', background: 'transparent', color: 'var(--c-terra-600)', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 13, cursor: 'pointer' }}>Remove</button>
          )}
          <div style={{ flex: 1, display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 10 }}>
            {savedFlash && (
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: 'var(--c-olive-600)', fontSize: 12, fontWeight: 500 }}>
                <img src="../assets/icons/success.svg" width={14} height={14} alt="" />
                Saved
              </span>
            )}
          </div>
          {step > 0 ? (
            <button onClick={goBack} style={{ height: 44, padding: '0 18px', borderRadius: 12, border: '1.5px solid var(--c-petrol-800)', background: 'transparent', color: 'var(--c-petrol-800)', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 13, cursor: 'pointer' }}>Back</button>
          ) : (
            <button onClick={onClose} style={{ height: 44, padding: '0 18px', borderRadius: 12, border: '1.5px solid var(--c-petrol-800)', background: 'transparent', color: 'var(--c-petrol-800)', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 13, cursor: 'pointer' }}>Cancel</button>
          )}
          <button onClick={goNext} disabled={parsing || (isLast ? !canSave : !stepValid[step])} style={{
            height: 44, padding: '0 22px', borderRadius: 12, border: 'none',
            background: (!parsing && (isLast ? canSave : stepValid[step])) ? 'var(--c-petrol-800)' : 'var(--c-cream-100)',
            color: (!parsing && (isLast ? canSave : stepValid[step])) ? '#fff' : 'var(--c-ink-300)',
            fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 13,
            boxShadow: (!parsing && (isLast ? canSave : stepValid[step])) ? 'var(--shadow-raised)' : 'none',
            cursor: (!parsing && (isLast ? canSave : stepValid[step])) ? 'pointer' : 'not-allowed',
          }}>
            {isLast ? (saving ? (pendingFile ? 'Uploading…' : 'Saving…') : (draftMode ? 'Add to employee' : (isNew ? 'Create skill' : 'Save changes'))) : 'Next'}
          </button>
          {saveError && (
            <span style={{ position: 'absolute', bottom: 64, right: 24, color: 'var(--c-terra-600, #C65A42)', fontSize: 12, fontFamily: "'Inter', sans-serif" }}>{saveError}</span>
          )}
        </footer>
      </aside>
      <style>{`
        @keyframes pr-fade { from { opacity: 0 } to { opacity: 1 } }
        @keyframes pr-slide { from { transform: translateX(24px); opacity: 0 } to { transform: translateX(0); opacity: 1 } }
      `}</style>
    </>
  );
}

const fieldInputStyle = {
  height: 44, padding: '0 14px', borderRadius: 12,
  background: 'var(--c-cream-50)',
  border: '1.5px solid var(--border-subtle)',
  boxShadow: 'var(--shadow-inset-input)',
  fontFamily: "'Suisse Int\\'l', sans-serif", fontSize: 14, color: 'var(--c-ink-900)',
  width: '100%', outline: 'none', boxSizing: 'border-box',
};

function ReviewSummary({ name, desc, inputs, apiConfig, file, onJump }) {
  const Row = ({ label, value, stepIdx }) => (
    <div style={{ display: 'flex', gap: 12, padding: '10px 0', borderTop: '1px solid var(--border-subtle)' }}>
      <div style={{ width: 110, flex: 'none', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 11, color: 'var(--c-ink-500)', letterSpacing: '0.06em', textTransform: 'uppercase', paddingTop: 2 }}>{label}</div>
      <div style={{ flex: 1, fontSize: 13, color: 'var(--c-ink-900)', minWidth: 0 }}>{value}</div>
      <button onClick={() => onJump(stepIdx)} style={{ height: 24, padding: '0 8px', borderRadius: 6, border: '1px solid var(--border-subtle)', background: 'var(--c-white)', color: 'var(--c-petrol-800)', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 11, cursor: 'pointer' }}>Edit</button>
    </div>
  );
  return (
    <div style={{ padding: '4px 14px 12px', borderRadius: 12, background: 'var(--c-white)', border: '1px solid var(--border-subtle)', boxShadow: 'var(--shadow-raised-sm)' }}>
      <div style={{ padding: '12px 0 8px', fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 500, fontSize: 14, color: 'var(--c-ink-900)' }}>Review</div>
      <Row label="Name" value={name || <span style={{ color: 'var(--c-ink-500)' }}>—</span>} stepIdx={0} />
      <Row label="Description" value={desc ? <span style={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{desc}</span> : <span style={{ color: 'var(--c-ink-500)' }}>—</span>} stepIdx={0} />
      <Row label="Inputs" value={inputs.length ? `${inputs.length} input${inputs.length === 1 ? '' : 's'} · ${inputs.map(i => i.name).slice(0, 3).join(', ')}${inputs.length > 3 ? '…' : ''}` : <span style={{ color: 'var(--c-ink-500)' }}>None</span>} stepIdx={1} />
      <Row label="API endpoints" value={(apiConfig && apiConfig.length) ? `${apiConfig.length} endpoint${apiConfig.length === 1 ? '' : 's'} · ${apiConfig.map(c => c.method + ' ' + (c.url || '')).slice(0, 2).join(', ')}${apiConfig.length > 2 ? '…' : ''}` : <span style={{ color: 'var(--c-ink-500)' }}>None (optional)</span>} stepIdx={2} />
      <Row label="File" value={file ? <span style={{ fontFamily: 'ui-monospace, "JetBrains Mono", Menlo, monospace', fontSize: 12 }}>{file.name}</span> : <span style={{ color: 'var(--c-terra-600)' }}>Required — none uploaded</span>} stepIdx={3} />
    </div>
  );
}

function InputRow({ value, onChange, onRemove }) {
  return (
    <div style={{ padding: 12, borderRadius: 12, background: 'var(--c-white)', border: '1px solid var(--border-subtle)', boxShadow: 'var(--shadow-raised-sm)', display: 'flex', flexDirection: 'column', gap: 8 }}>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
        <input value={value.name} onChange={e => onChange({ name: e.target.value.replace(/\s+/g, '_').toLowerCase() })}
          placeholder="input_name"
          style={{ ...fieldInputStyle, flex: 1, height: 36, fontFamily: 'ui-monospace, "JetBrains Mono", Menlo, monospace', fontSize: 13 }} />
        <button onClick={onRemove} title="Remove input" style={{ width: 36, height: 36, borderRadius: 10, border: '1px solid var(--border-subtle)', background: 'var(--c-cream-50)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: 'none' }}>
          <img src="../assets/icons/cross.svg" width={11} height={11} alt="" />
        </button>
      </div>
      <textarea value={value.description} onChange={e => onChange({ description: e.target.value })}
        placeholder="Describe this input — guides the LLM at run time."
        rows={2}
        style={{ ...fieldInputStyle, height: 'auto', minHeight: 56, padding: '10px 14px', resize: 'vertical', lineHeight: 1.45 }} />
    </div>
  );
}

function Field({ label, hint, ai, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      <div>
        <div style={{ fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 500, fontSize: 14, color: 'var(--c-ink-900)', display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
          <span>{label}</span>
          {ai && <AiBadge />}
        </div>
        {hint && <div style={{ fontSize: 12, color: 'var(--c-ink-500)', marginTop: 2 }}>{hint}</div>}
      </div>
      {children}
    </div>
  );
}

// Shown next to any field whose value was filled by the backend's LLM
// extractor (Portkey → Sonnet 4.6) rather than by SKILL.md frontmatter
// or the user. The flag clears as soon as the user edits the field, so
// the badge disappears the moment a value has been "verified" by them.
function AiBadge() {
  return (
    <span title="Auto-filled by AI — please verify" style={{
      display: 'inline-flex', alignItems: 'center', gap: 5,
      marginLeft: 8, padding: '2px 8px', borderRadius: 999,
      background: 'var(--c-petrol-050, rgba(23,53,64,0.08))',
      color: 'var(--c-petrol-800)',
      fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 9,
      letterSpacing: '0.08em', textTransform: 'uppercase',
      border: '1px solid var(--border-subtle)',
    }}>
      <span style={{ width: 5, height: 5, borderRadius: 999, background: 'var(--c-petrol-800)' }} />
      AI-suggested
    </span>
  );
}

// Replaces the entire step body while /api/skills/parse is in flight.
// We intentionally don't render any of the form fields during parsing —
// the whole form appears at once when the response lands, fully populated
// by the deterministic parser + LLM fallback. No progressive fill, no
// flicker between "frontmatter loaded" and "LLM finished."
function ParseLoader() {
  return (
    <div style={{
      flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      gap: 14, padding: '40px 20px', minHeight: 240,
    }}>
      <div style={{
        width: 36, height: 36, borderRadius: 999,
        border: '3px solid var(--c-cream-100)',
        borderTopColor: 'var(--c-petrol-800)',
        animation: 'pr-spin 0.9s linear infinite',
      }} />
      <div style={{ fontFamily: "'Suisse Int\\'l', sans-serif", fontWeight: 500, fontSize: 14, color: 'var(--c-ink-900)' }}>
        Parsing skill bundle…
      </div>
      <div style={{ fontSize: 12, color: 'var(--c-ink-500)', textAlign: 'center', maxWidth: 320, lineHeight: 1.45 }}>
        Reading SKILL.md and filling in any missing fields. Hang tight — everything will appear at once.
      </div>
      <style>{`@keyframes pr-spin { to { transform: rotate(360deg) } }`}</style>
    </div>
  );
}

function DropZone({ drag, setDrag, onFiles, onBrowse }) {
  return (
    <div onDragOver={e => { e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)}
      onDrop={e => { e.preventDefault(); setDrag(false); onFiles(e.dataTransfer.files); }} onClick={onBrowse}
      style={{ borderRadius: 16, padding: '32px 20px', cursor: 'pointer', background: drag ? 'var(--c-petrol-050)' : 'var(--c-white)', border: `2px dashed ${drag ? 'var(--c-petrol-500)' : 'var(--border-default)'}`, boxShadow: 'var(--shadow-raised-sm)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10, textAlign: 'center', transition: 'all .15s ease-out' }}>
      <div style={{ width: 44, height: 44, borderRadius: 12, background: 'var(--c-petrol-050)', color: 'var(--c-petrol-800)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <img src="../assets/icons/plus.svg" width={18} height={18} alt="" />
      </div>
      <div>
        <div style={{ fontWeight: 500, fontSize: 14, color: 'var(--c-ink-900)' }}>Drop a file here</div>
        <div style={{ fontSize: 12, color: 'var(--c-ink-500)', marginTop: 4 }}>or click to browse</div>
      </div>
    </div>
  );
}

function FilePreview({ file, onReplace, onClear, isNew }) {
  const fmt = (b) => b >= 1024 ? (b/1024).toFixed(1) + ' KB' : b + ' B';
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, padding: 14, borderRadius: 16, background: 'var(--c-white)', border: '1px solid var(--border-subtle)', boxShadow: 'var(--shadow-raised-sm)' }}>
      <div style={{ width: 44, height: 44, borderRadius: 10, flex: 'none', background: 'var(--c-petrol-800)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 10, letterSpacing: '0.05em' }}>FILE</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: 'ui-monospace, "JetBrains Mono", Menlo, monospace', fontWeight: 500, fontSize: 13, color: 'var(--c-ink-900)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{file.name}</div>
        <div style={{ fontFamily: "'Inter', sans-serif", fontSize: 11, fontWeight: 500, color: 'var(--c-ink-500)', letterSpacing: '0.02em', marginTop: 4, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          <span>{fmt(file.size)}</span><span>·</span><span>v{file.version}</span><span>·</span><span>updated {file.updated}</span>
        </div>
      </div>
      <div style={{ display: 'flex', gap: 6 }}>
        {!isNew && (
          <button title="Download current file" style={iconBtn}>
            <img src="../assets/icons/down.svg" width={12} height={12} alt="Download" />
          </button>
        )}
        <button onClick={onReplace} style={{ height: 36, padding: '0 14px', borderRadius: 10, border: '1.5px solid var(--c-petrol-800)', background: 'var(--c-white)', color: 'var(--c-petrol-800)', fontFamily: "'Inter', sans-serif", fontWeight: 700, fontSize: 12, cursor: 'pointer' }}>Replace</button>
        {onClear && (
          <button onClick={onClear} title="Remove file" style={iconBtn}>
            <img src="../assets/icons/cross.svg" width={11} height={11} alt="" />
          </button>
        )}
      </div>
    </div>
  );
}

const iconBtn = {
  width: 36, height: 36, borderRadius: 10,
  background: 'var(--c-white)', border: '1px solid var(--border-subtle)',
  boxShadow: 'var(--shadow-raised-sm)', cursor: 'pointer',
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
};

Object.assign(window, { Drawer, SkillEditDrawer: Drawer });
