/* Full character detail view — every field editable.
   Server persistence: named columns (name/archetype/motivation/vulnerability/voice/
   appearance/voice_asset_id/lip_sync_target) plus an extras JSON column that
   captures the design's rich fields (dialect, costume, quirks, catchphrases,
   props, signature_moves, continuity_notes, forbidden_changes, negative_prompt,
   role/age/pronouns/summary/flaw/backstory/arc, etc.). */

const CharacterDetail = ({ char, refAsset, clonedVoiceId, onClose, onBack }) => {
  const [tab, setTab] = React.useState("identity");
  const [local, setLocal] = React.useState(char);
  const [currentRef, setCurrentRef] = React.useState(refAsset || null);
  const [voiceId, setVoiceId] = React.useState(clonedVoiceId || null);
  const [regenerating, setRegenerating] = React.useState(false);
  const [cloning, setCloning] = React.useState(false);
  const [voiceErr, setVoiceErr] = React.useState(null);
  const fileInputRef = React.useRef(null);
  React.useEffect(() => { setLocal(char); setCurrentRef(refAsset || null); setVoiceId(clonedVoiceId || null); }, [char.id, refAsset, clonedVoiceId]);

  const isLive = Boolean(window.CinematonAPI && window.CinematonAPI.live);

  const regenReference = async () => {
    if (!isLive) return;
    setRegenerating(true);
    try {
      const asset = await window.CinematonAPI.regenCharacterReference(char.id);
      setCurrentRef(asset);
      window.dispatchEvent(new CustomEvent('cinematon:refresh-run'));
    } catch (err) { console.error('regen reference failed', err); }
    finally { setRegenerating(false); }
  };

  const cloneVoice = async (event) => {
    const files = Array.from(event.target.files || []);
    if (!files.length || !isLive) return;
    setCloning(true); setVoiceErr(null);
    try {
      const result = await window.CinematonAPI.cloneCharacterVoice(char.id, files);
      setVoiceId(result.voice_id);
      window.dispatchEvent(new CustomEvent('cinematon:refresh-run'));
    } catch (err) { setVoiceErr(err); console.error('voice clone failed', err); }
    finally { setCloning(false); event.target.value = ''; }
  };

  // patch(field, value) updates local state immediately and PATCHes the server
  // in live mode. Supports nested dot-paths (e.g., "appearance.height").
  const patch = (field, value) => {
    setLocal((cur) => setDeep({ ...cur }, field, value));
    if (isLive && local && local.id) {
      const body = setDeep({}, field, value);
      window.CinematonAPI.patchCharacter(local.id, body)
        .catch((err) => console.error(`character ${field} patch failed`, err));
    }
  };

  const c = local;

  return (
    <div className="char-detail">
      <div className="cd-header">
        <button className="btn ghost sm" onClick={onBack}><Icon name="chev-l" size={11}/> All characters</button>
        <div className="cd-id">
          <span className="cd-eyebrow">{(c.role || "supporting").toUpperCase()} · {(c.archetype || "").replace(/_/g, " ").toUpperCase()}</span>
          <h1 className="serif" style={{ margin: "2px 0 0", fontSize: 30, fontWeight: 500 }}>
            <EditableField value={c.name} serif onSave={(v) => patch("name", v)}/>
          </h1>
          <div className="dim mono" style={{ fontSize: 11.5, marginTop: 4 }}>{c.id} · age {c.age || "?"} · {c.pronouns || "?"}</div>
        </div>
        <div className="row gap-sm">
          <button className="btn ghost"><Icon name="lock" size={11}/> Lock to canon</button>
          <button className="btn"><Icon name="eye"/> Open in Script</button>
        </div>
      </div>

      <BuilderBar
        agent="Character agent · horror.v1"
        grounded={`brief · mini-bible · v${1.3}`}
        status={{ label: isLive ? "live · saves on blur" : "fixture · local edits only", tone: "ok" }}
        placeholder="Refine motivation, voice, backstory…"
      />

      <div className="cd-body">
        <aside className="cd-left">
          <div className="cd-portrait">
            {currentRef && window.CinematonAPI ? (
              <img
                src={window.CinematonAPI.assetFileUrl(currentRef.asset_id)}
                alt={`reference plate for ${c.name}`}
                style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
                onError={(e) => { e.currentTarget.style.display = 'none'; }}
              />
            ) : (
              <>
                <div className="thumb-placeholder" data-label={(c.name || "").split(" ")[0]}/>
                <div className="thumb-noir"/>
              </>
            )}
            <div className="cd-portrait-ovl">
              <span className="mono dim" style={{ fontSize: 10 }}>
                REFERENCE · {currentRef ? currentRef.asset_id.slice(-8) : '—'}
              </span>
              <button className="btn ghost sm" onClick={regenReference} disabled={!isLive || regenerating}>
                <Icon name="regen" size={10}/> {regenerating ? '…' : 'Regen'}
              </button>
            </div>
          </div>
          <div className="cd-portrait-meta">
            <CDPair label="Voice asset"   value={c.voice_asset_id   || ""} mono onSave={(v) => patch("voice_asset_id",   v)}/>
            <CDPair label="Voice provider" value={c.voice_provider   || ""} mono onSave={(v) => patch("voice_provider",   v)}/>
            <CDPair label="Lip-sync target" value={c.lip_sync_target || ""} mono onSave={(v) => patch("lip_sync_target", v)}/>
            <div style={{ marginTop: 10, padding: '8px 0', borderTop: '1px solid var(--line)' }}>
              <div className="dim mono" style={{ fontSize: 10, marginBottom: 6 }}>
                ELEVENLABS VOICE: {voiceId ? voiceId.slice(0, 14) + '…' : 'default'}
              </div>
              <input
                ref={fileInputRef} type="file" accept="audio/*" multiple
                style={{ display: 'none' }} onChange={cloneVoice}
              />
              <button
                className="btn ghost sm"
                style={{ width: "100%", justifyContent: "center" }}
                onClick={() => fileInputRef.current && fileInputRef.current.click()}
                disabled={!isLive || cloning}
              >
                <Icon name="upload" size={11}/> {cloning ? 'Cloning…' : voiceId ? 'Re-clone voice' : 'Clone voice from sample'}
              </button>
              {voiceErr && (
                <div className="dim" style={{ fontSize: 10, marginTop: 6, color: 'var(--bad)' }}>
                  {voiceErr.message || 'Voice clone failed'}
                </div>
              )}
            </div>
          </div>

          <nav className="cd-tabs">
            {[
              ["identity",     "Identity & arc"],
              ["voice",        "Voice & speech"],
              ["appearance",   "Appearance & costume"],
              ["behavior",     "Behavior & props"],
              ["relationships","Relationships"],
              ["continuity",   "Continuity & rights"],
            ].map(([k, label]) => (
              <button key={k} onClick={() => setTab(k)} className={`cd-tab ${tab === k ? "active" : ""}`}>
                <span className="cd-tab-bar"/> {label}
              </button>
            ))}
          </nav>

          <div className="cd-tags">
            {(c.tags || []).map(t => <span key={t} className="tag">{t}</span>)}
          </div>
        </aside>

        <section className="cd-center scroll">
          {tab === "identity"      && <CharIdentity     c={c} patch={patch}/>}
          {tab === "voice"         && <CharVoice        c={c} patch={patch}/>}
          {tab === "appearance"    && <CharAppearance   c={c} patch={patch}/>}
          {tab === "behavior"      && <CharBehavior     c={c} patch={patch}/>}
          {tab === "relationships" && <CharRelationships c={c} patch={patch}/>}
          {tab === "continuity"    && <CharContinuity   c={c} patch={patch}/>}
        </section>

        <aside className="cd-right scroll">
          <div className="cd-rail-head">
            <Sparkle size={11}/>
            <span className="cd-rail-title">Open proposals</span>
            <span className="cd-rail-count">2</span>
          </div>
          <Proposal
            kind="Voice agent"
            title="Tighten dialect notes"
            body={<p>Add a single regional vowel cue so the TTS reference set has something to anchor on.</p>}
            diff={[{ op: "mod", field: "dialect", value: "American English, Midwestern flatness — short 'a' especially" }]}
            refs={["voice_mara_v3"]}
            onAccept={() => {}} onRefine={() => {}} onReject={() => {}}
          />
          <hr className="hr"/>
          <div className="cd-rail-head">
            <Icon name="link" size={11}/>
            <span className="cd-rail-title">Canon usage</span>
          </div>
          <div className="cd-usage">
            <div className="usage-row"><span className="lbl">Scenes</span><span className="mono">— / —</span></div>
            <div className="usage-row"><span className="lbl">Shots</span><span className="mono">— / —</span></div>
            <div className="usage-row"><span className="lbl">Dialogue lines</span><span className="mono">—</span></div>
          </div>
        </aside>
      </div>
    </div>
  );
};

/* ====== Editable field primitives ====== */

const CDField = ({ label, hint, value, multiline, onSave, mono, serif, placeholder }) => (
  <div className="cd-field">
    <div className="cd-field-head">
      <span className="cd-field-lbl">{label}</span>
      {hint && <span className="cd-field-hint">{hint}</span>}
    </div>
    <div className="cd-field-val">
      {onSave
        ? <EditableField value={value ?? ""} multiline={multiline} autosize={multiline} mono={mono} serif={serif} placeholder={placeholder} onSave={onSave}/>
        : (value ?? "")}
    </div>
  </div>
);

const CDPair = ({ label, value, mono, onSave }) => (
  <div className="cd-pm-row">
    <span className="lbl">{label}</span>
    <EditableField value={value} mono={mono} onSave={onSave}/>
  </div>
);

// CDList — editable list of strings (one item per line, save on blur).
const CDList = ({ label, hint, items, onSave }) => {
  const text = (items || []).join("\n");
  return (
    <div className="cd-field">
      <div className="cd-field-head">
        <span className="cd-field-lbl">{label}</span>
        {hint && <span className="cd-field-hint">{hint}</span>}
      </div>
      <div className="cd-field-val">
        <EditableField
          value={text} multiline autosize
          placeholder="One item per line"
          onSave={(v) => onSave(v.split("\n").map((s) => s.trim()).filter(Boolean))}
        />
      </div>
    </div>
  );
};

/* ====== Tab bodies ====== */

const CharIdentity = ({ c, patch }) => (
  <div className="cd-tab-pane">
    <div className="cd-grid-2">
      <CDField label="Role"       hint="protagonist / supporting / antagonist" value={c.role}       onSave={(v) => patch("role", v)}/>
      <CDField label="Archetype"  hint="reluctant_witness, knowing_warden..."  value={c.archetype}  onSave={(v) => patch("archetype", v)}/>
      <CDField label="Age"        value={c.age}        onSave={(v) => patch("age", Number(v) || v)}/>
      <CDField label="Pronouns"   value={c.pronouns}   onSave={(v) => patch("pronouns", v)}/>
    </div>
    <CDField label="Summary"      hint="one paragraph"          value={c.summary}      multiline onSave={(v) => patch("summary", v)}/>
    <div className="cd-grid-2">
      <CDField label="Motivation" hint="what they want"         value={c.motivation}   onSave={(v) => patch("motivation", v)}/>
      <CDField label="Flaw"       hint="what undoes them"       value={c.flaw}         onSave={(v) => patch("flaw", v)}/>
      <CDField label="Vulnerability" hint="genre hook"          value={c.vulnerability} onSave={(v) => patch("vulnerability", v)}/>
      <CDField label="Arc"        hint="across the 3 minutes"   value={c.arc}          onSave={(v) => patch("arc", v)}/>
    </div>
    <CDField label="Backstory"    hint="referenced, not shown"  value={c.backstory}    multiline onSave={(v) => patch("backstory", v)}/>
  </div>
);

const CharVoice = ({ c, patch }) => (
  <div className="cd-tab-pane">
    <div className="cd-grid-2">
      <CDField label="Voice"        value={c.voice}        onSave={(v) => patch("voice", v)}/>
      <CDField label="Dialect"      value={c.dialect}      onSave={(v) => patch("dialect", v)}/>
      <CDField label="Speech pace"  value={c.speech_pace}  onSave={(v) => patch("speech_pace", v)}/>
      <CDField label="Vocabulary"   value={c.vocabulary}   onSave={(v) => patch("vocabulary", v)}/>
    </div>
    <CDList   label="Quirks"        hint="behavioral tics"       items={c.quirks}       onSave={(v) => patch("quirks", v)}/>
    <CDList   label="Catchphrases"  hint="reusable in dialogue"  items={c.catchphrases} onSave={(v) => patch("catchphrases", v)}/>
    <CDField  label="Voice reference" hint="recording notes"     value={c.voice_reference} onSave={(v) => patch("voice_reference", v)}/>
  </div>
);

const CharAppearance = ({ c, patch }) => {
  const a = c.appearance && typeof c.appearance === "object" ? c.appearance : {};
  const cos = c.costume && typeof c.costume === "object" ? c.costume : {};
  // appearance can be either a string (server-named column) OR a rich object (extras).
  // Support both: if object, render fields; if string, render single editable.
  const isObject = c.appearance && typeof c.appearance === "object";

  return (
    <div className="cd-tab-pane">
      {isObject ? (
        <div className="cd-grid-3">
          <CDField label="Height" value={a.height} onSave={(v) => patch("appearance.height", v)}/>
          <CDField label="Build"  value={a.build}  onSave={(v) => patch("appearance.build", v)}/>
          <CDField label="Hair"   value={a.hair}   onSave={(v) => patch("appearance.hair", v)}/>
          <CDField label="Eyes"   value={a.eyes}   onSave={(v) => patch("appearance.eyes", v)}/>
          <CDField label="Skin"   value={a.skin}   onSave={(v) => patch("appearance.skin", v)}/>
          <CDField label="Marks"  hint="continuity-critical" value={a.identifying_marks} onSave={(v) => patch("appearance.identifying_marks", v)}/>
        </div>
      ) : (
        <CDField label="Appearance" hint="full description" value={c.appearance} multiline onSave={(v) => patch("appearance", v)}/>
      )}

      <CDField label="Primary costume" hint="default across all shots" value={cos.primary || c.costume_primary} multiline onSave={(v) => patch(isObject ? "costume.primary" : "costume_primary", v)}/>
      <CDField label="Cold-weather variant" value={cos.cold_weather || c.costume_cold} onSave={(v) => patch(isObject ? "costume.cold_weather" : "costume_cold", v)}/>
      <CDField label="Signature item" hint="never absent" value={cos.signature_item || c.costume_signature} onSave={(v) => patch(isObject ? "costume.signature_item" : "costume_signature", v)}/>
    </div>
  );
};

const CharBehavior = ({ c, patch }) => (
  <div className="cd-tab-pane">
    <CDList label="Props"            hint="stays with character through cuts" items={c.props}           onSave={(v) => patch("props", v)}/>
    <CDList label="Signature moves"  hint="embodied action lines"             items={c.signature_moves} onSave={(v) => patch("signature_moves", v)}/>
  </div>
);

const CharRelationships = ({ c, patch }) => {
  const rels = c.relationships || [];
  const updateRel = (idx, patchObj) => {
    const next = rels.map((r, i) => i === idx ? { ...r, ...patchObj } : r);
    patch("relationships", next);
  };
  const removeRel = (idx) => {
    const next = rels.filter((_, i) => i !== idx);
    patch("relationships", next);
  };
  const addRel = () => {
    patch("relationships", [...rels, { with: "char_other", role: "ally", note: "" }]);
  };

  return (
    <div className="cd-tab-pane">
      <CDField label="Relationships" hint="who they know"
        value={rels.length === 0 ? "(none yet)" : ""}
      />
      <div className="cd-rel-list">
        {rels.map((r, i) => (
          <div key={i} className="cd-rel-row">
            <div className="cd-rel-with">
              <div className="cd-rel-avatar"><div className="thumb-placeholder" data-label={(r.with || "??").replace("char_","").slice(0,2).toUpperCase()}/></div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <EditableField value={r.with || ""} mono onSave={(v) => updateRel(i, { with: v })}/>
                <EditableField value={r.role || ""} placeholder="role" onSave={(v) => updateRel(i, { role: v })}/>
              </div>
            </div>
            <div className="cd-rel-note">
              <EditableField value={r.note || ""} multiline placeholder="note" onSave={(v) => updateRel(i, { note: v })}/>
            </div>
            <div className="cd-rel-actions">
              <button className="btn ghost sm" onClick={() => removeRel(i)}>⌫</button>
            </div>
          </div>
        ))}
      </div>
      <button className="btn ghost" onClick={addRel} style={{ marginTop: 12 }}>+ Add relationship</button>
    </div>
  );
};

const CharContinuity = ({ c, patch }) => (
  <div className="cd-tab-pane">
    <CDList  label="Continuity notes"  hint="checked on every render packet" items={c.continuity_notes} onSave={(v) => patch("continuity_notes", v)}/>
    <CDList  label="Forbidden changes" hint="hard guardrails"                items={c.forbidden_changes} onSave={(v) => patch("forbidden_changes", v)}/>
    <CDField label="Negative prompt"   hint="injected into every shot prompt" value={c.negative_prompt} multiline onSave={(v) => patch("negative_prompt", v)}/>
    <CDField label="Reference image ID" mono value={c.reference_image_id} onSave={(v) => patch("reference_image_id", v)}/>
  </div>
);

// ---- helpers ----

function setDeep(obj, path, value) {
  const parts = path.split(".");
  let cur = obj;
  for (let i = 0; i < parts.length - 1; i++) {
    const k = parts[i];
    if (cur[k] == null || typeof cur[k] !== "object") cur[k] = {};
    else cur[k] = { ...cur[k] };
    cur = cur[k];
  }
  cur[parts[parts.length - 1]] = value;
  return obj;
}

window.CharacterDetail = CharacterDetail;
