// Left panel — upload, view detection, presets, warp sliders, heal CTA.

// All warp axes at rest. Kept here (not shared from app.jsx — classic <script>
// scopes don't cross) so resets/presets always cover every axis.
const ZERO_WARP = { alar: 0, hump: 0, tipLift: 0, humpProf: 0, tipRot: 0, bridgeWidth: 0, tipWidth: 0, noseLen: 0 };

const Slider = ({ name, value, onChange, min = 0, max = 1, step = 0.01, unit = "", harmony = [0.2, 0.6], tooltip, surgicalNote }) => {
  const pct = ((value - min) / (max - min)) * 100;
  const hPctStart = ((harmony[0] - min) / (max - min)) * 100;
  const hPctEnd = ((harmony[1] - min) / (max - min)) * 100;
  const outOfHarmony = value > harmony[1] || value < harmony[0];
  const displayValue = max <= 1 ? value.toFixed(2) : value.toFixed(1);

  const handleDrag = (e) => {
    const track = e.currentTarget.getBoundingClientRect();
    const move = (ev) => {
      const x = (ev.clientX - track.left) / track.width;
      const v = Math.max(min, Math.min(max, min + x * (max - min)));
      onChange(Math.round(v / step) * step);
    };
    const up = () => {
      window.removeEventListener("mousemove", move);
      window.removeEventListener("mouseup", up);
    };
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseup", up);
    move(e);
  };

  return (
    <div className="slider-block">
      <div className="slider-head">
        <div className="slider-name tip-anchor">
          {name}
          <span className="info">i</span>
          {tooltip && (
            <div className="tip">
              <div className="label">What this does</div>
              {tooltip}
              {surgicalNote && (
                <>
                  <div className="label" style={{ marginTop: 8 }}>What surgeons change</div>
                  {surgicalNote}
                </>
              )}
            </div>
          )}
        </div>
        <div className="slider-value">
          {displayValue}<span className="unit">{unit}</span>
        </div>
      </div>
      <div className="slider-track" onMouseDown={handleDrag}>
        <div className="slider-rail">
          <div className="slider-range" style={{ left: `${hPctStart}%`, width: `${hPctEnd - hPctStart}%` }} />
          <div className="slider-fill" style={{ width: `${pct}%` }} />
          <div className={`slider-thumb ${outOfHarmony ? "warn" : ""}`} style={{ left: `${pct}%` }} />
        </div>
      </div>
      <div className="slider-meta">
        <span>{min}{unit}</span>
        <span className={`harmony ${outOfHarmony ? "warn" : ""}`}>
          {outOfHarmony ? "OUTSIDE HARMONIOUS RANGE" : "WITHIN HARMONIOUS RANGE"}
        </span>
        <span>{max}{unit}</span>
      </div>
    </div>
  );
};

// FRD adjustable-parameter catalog. `live` params map to a real warp axis and
// the view(s) they apply to; preview params are shown disabled for completeness.
const PARAM_CATEGORIES = [
  { name: "Radix / Bridge", params: [
    { label: "Dorsal hump reduction", live: true, warpKey: "hump", views: ["frontal", "three_quarter"], harmony: [0.1, 0.55], tooltip: "Smooths the bony-cartilaginous bridge convexity.", surgicalNote: "Rasping + cartilage resection." },
    { label: "Hump reduction (profile)", live: true, warpKey: "humpProf", views: ["profile", "three_quarter"], harmony: [0.15, 0.6], tooltip: "Reduces dorsal projection from the side.", surgicalNote: "Osteotomies + dorsal take-down." },
    { label: "Bridge width", live: true, warpKey: "bridgeWidth", views: ["frontal", "three_quarter"], harmony: [0, 0.45], tooltip: "Narrows the bony bridge sidewalls toward the midline.", surgicalNote: "Lateral + medial osteotomies." },
    { label: "Radix height", live: false },
    { label: "Bridge height", live: false },
    { label: "Bridge curvature", live: false },
  ]},
  { name: "Tip", params: [
    { label: "Tip lift", live: true, warpKey: "tipLift", views: ["frontal", "three_quarter"], harmony: [0, 0.15], tooltip: "Rotates the tip cephalad. Small values feel natural.", surgicalNote: "Cephalic trim + tip suture." },
    { label: "Tip rotation", live: true, warpKey: "tipRot", views: ["profile", "three_quarter"], min: 0, max: 15, step: 0.1, unit: "°", harmony: [2, 8], tooltip: "Rotates the tip upward.", surgicalNote: "Septal extension grafts or columellar strut." },
    { label: "Tip width", live: true, warpKey: "tipWidth", views: ["frontal", "three_quarter"], harmony: [0, 0.4], tooltip: "Refines a wide or bulbous tip by drawing the tip-defining points inward.", surgicalNote: "Cephalic trim + dome suturing." },
    { label: "Tip projection", live: false },
    { label: "Tip definition", live: false },
    { label: "Tip sharpness", live: false },
  ]},
  { name: "Base", params: [
    { label: "Alar narrowing", live: true, warpKey: "alar", views: ["frontal", "three_quarter"], harmony: [0, 0.35], tooltip: "Reduces nostril-base spread.", surgicalNote: "Alar base resection." },
    { label: "Nostril size", live: false },
    { label: "Nostril symmetry", live: false },
    { label: "Columella visibility", live: false },
    { label: "Alar groove depth", live: false },
  ]},
  { name: "Overall shape", params: [
    { label: "Nose length", live: true, warpKey: "noseLen", views: ["frontal", "three_quarter"], min: 0, max: 0.4, step: 0.01, harmony: [0, 0.2], tooltip: "Shortens the nose by drawing the base up toward the radix.", surgicalNote: "Caudal septal resection + tip set-back." },
    { label: "Nose projection ratio", live: false },
    { label: "Supratip break intensity", live: false },
    { label: "Feminine / masculine balance", live: false },
  ]},
];

// Params without a real warp axis render as a compact "coming soon" row rather
// than a full disabled slider — a panel full of greyed-out sliders reads as
// broken; a short list of upcoming params reads as intentional roadmap.
const PreviewSlider = ({ label }) => (
  <div className="param-preview" aria-disabled="true" title="Parameter planned for a future build — not yet interactive">
    <span className="param-preview-name">{label}</span>
    <span className="param-preview-tag">soon</span>
  </div>
);

const ParamCategory = ({ cat, warp, view, updateWarp, open, onToggle }) => {
  const liveCount = cat.params.filter(p => p.live && (!p.views || p.views.includes(view))).length;
  return (
    <div className="param-cat">
      <button className="param-cat-head" onClick={onToggle}>
        <span className={`caret ${open ? "open" : ""}`}>▸</span>
        <span className="param-cat-name">{cat.name}</span>
        <span className="param-cat-count">{liveCount} live · {cat.params.length} total</span>
      </button>
      {open && (
        <div className="param-cat-body">
          {cat.params.map((p) => {
            if (!p.live) return <PreviewSlider key={p.label} label={p.label} />;
            const applies = !p.views || p.views.includes(view);
            if (!applies) return <PreviewSlider key={p.label} label={`${p.label} (other view)`} />;
            return (
              <Slider
                key={p.label}
                name={p.label}
                value={warp[p.warpKey]}
                onChange={(v) => updateWarp({ [p.warpKey]: v })}
                min={p.min ?? 0} max={p.max ?? 1} step={p.step ?? 0.01} unit={p.unit ?? ""}
                harmony={p.harmony}
                tooltip={p.tooltip}
                surgicalNote={p.surgicalNote}
              />
            );
          })}
        </div>
      )}
    </div>
  );
};

const PRESETS = {
  "Custom": null,
  "Dorsal Hump Reduction": { alar: 0, hump: 0.9, tipLift: 0, humpProf: 0.9, tipRot: 0 },
  "Tip Refinement": { alar: 0.12, hump: 0, tipLift: 0.13, humpProf: 0, tipRot: 12 },
  "Ethnic Preservation": { alar: 0, hump: 0.3, tipLift: 0.04, humpProf: 0.25, tipRot: 4 },
};

const PresetChips = ({ active, onChange }) => (
  <div className="preset-chips">
    {Object.keys(PRESETS).map(p => (
      <button key={p} className={`chip ${active === p ? "active" : ""}`} onClick={() => onChange(p)}>
        {p}
      </button>
    ))}
  </div>
);

const DetectionRow = ({ label, value, meta }) => (
  <div className="detection">
    <div><div className="label">{label}</div></div>
    <div className="value">{value}</div>
    <div className="meta">{meta}</div>
  </div>
);

const UploadZone = ({ ctx }) => {
  const fileInputRef = React.useRef(null);
  const [dragging, setDragging] = React.useState(false);
  const [samples, setSamples] = React.useState([]);

  React.useEffect(() => {
    api.samples().then(setSamples).catch(() => {});
  }, []);

  const onDrop = (e) => {
    e.preventDefault();
    setDragging(false);
    const file = e.dataTransfer.files?.[0];
    if (file) ctx.loadFile(file);
  };

  const onPick = (e) => {
    const file = e.target.files?.[0];
    if (file) ctx.loadFile(file);
    e.target.value = "";
  };

  const loading = ctx.state.loadingSession;

  return (
    <>
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*"
        style={{ display: "none" }}
        onChange={onPick}
      />
      <div
        className="dropzone"
        style={{
          borderColor: dragging ? "var(--ink)" : undefined,
          background: dragging ? "var(--bg-2)" : undefined,
          opacity: loading ? 0.5 : 1,
          cursor: loading ? "wait" : "pointer",
        }}
        onClick={() => !loading && fileInputRef.current?.click()}
        onDragOver={(e) => { e.preventDefault(); setDragging(true); }}
        onDragEnter={(e) => { e.preventDefault(); setDragging(true); }}
        onDragLeave={() => setDragging(false)}
        onDrop={onDrop}
      >
        <div className="icon">↑</div>
        <div className="hint">{loading ? "Detecting face…" : (dragging ? "Drop to upload" : "Drag a portrait here")}</div>
        <div className="dropzone-actions">
          <button className="btn" onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }} disabled={loading}>Browse</button>
        </div>
      </div>
      <div className="mono-tiny" style={{ marginBottom: 8 }}>Or try a sample portrait ({samples.length})</div>
      <div className="samples" style={{ gridTemplateColumns: "repeat(5, 1fr)" }}>
        {samples.map(s => (
          <div
            key={s.n}
            className="sample"
            onClick={() => !loading && ctx.loadSample(s.n)}
            title={`face_${s.tag}.jpg · ${s.view}`}
            style={{ overflow: "hidden" }}
          >
            <img
              src={`/api/sample-thumb/${s.n}`}
              onError={(e) => { e.target.style.display = "none"; }}
              style={{ width: "100%", height: "100%", objectFit: "cover" }}
              alt={s.tag}
            />
            <span className="tag">{s.tag}</span>
          </div>
        ))}
      </div>
    </>
  );
};

const LeftPanel = ({ ctx }) => {
  const { state, set, triggerWarp, runHeal } = ctx;
  const session = state.session;
  const view = state.view;
  const [openCats, setOpenCats] = React.useState({ "Radix / Bridge": true, "Tip": true, "Base": true, "Overall shape": false });
  const toggleCat = (name) => setOpenCats((o) => ({ ...o, [name]: !o[name] }));
  const isFrontalish = view === "frontal" || view === "three_quarter" || view === "three-quarter";
  const isProfileish = view === "profile" || view === "three_quarter" || view === "three-quarter";

  const updateWarp = (patch) => {
    const next = { ...state.warp, ...patch };
    triggerWarp(next);
  };

  const applyPreset = (name) => {
    set({ preset: name });
    const cfg = PRESETS[name];
    if (cfg) triggerWarp({ ...ZERO_WARP, ...cfg });
    else triggerWarp({ ...state.warp });
  };

  const resetWarp = () => {
    set({ preset: "Custom" });
    triggerWarp({ ...ZERO_WARP });
  };

  return (
    <div className="panel left">
      {/* 01 INPUT */}
      <div className="panel-section">
        <div className="section-label">
          <span><span className="num">01</span>Input</span>
          {session && <button className="btn-ghost btn" onClick={() => set({ session: null, results: [], warp: { ...ZERO_WARP }, deltas: {}, activeResult: null, warpImageUrl: null })}>Reset</button>}
        </div>

        {!session ? (
          <div className="mono-tiny" style={{ color: "var(--ink-3)" }}>Add a portrait in step 1 first.</div>
        ) : (
          <>
            <DetectionRow label="Face" value="Detected" meta={`${session.landmarks_count} pts`} />
            <DetectionRow
              label="View"
              value={session.view.tag.replace("_", "-").replace(/^./, c => c.toUpperCase())}
              meta={`yaw ${session.view.yaw}°`}
            />
            <DetectionRow label="Source" value={session.source} meta="" />
            <div className="divider"></div>
            <div className="row-between mono-tiny">
              <span>VIEW OVERRIDE</span>
              <div style={{ display: "flex", gap: 4 }}>
                {[["frontal", "F"], ["three_quarter", "3/4"], ["profile", "P"]].map(([v, label]) => (
                  <button
                    key={v}
                    className={`chip ${view === v ? "active" : ""}`}
                    style={{ padding: "4px 8px" }}
                    onClick={() => set({ view: v })}
                  >
                    {label}
                  </button>
                ))}
              </div>
            </div>
          </>
        )}
      </div>

      {/* 02 PRESETS */}
      {session && (
        <div className="panel-section">
          <div className="section-label">
            <span><span className="num">02</span>Approach</span>
            <span className="mono-tiny">Preset</span>
          </div>
          <PresetChips active={state.preset} onChange={applyPreset} />
          <div className="mono-tiny" style={{ textTransform: "none", fontSize: 11, color: "var(--ink-3)", lineHeight: 1.5 }}>
            <em style={{ fontFamily: "var(--serif)", fontStyle: "italic" }}>{state.preset}</em> — {
              state.preset === "Dorsal Hump Reduction" ? "Reduces dorsal convexity while preserving tip projection. Bridge-only edit."
              : state.preset === "Tip Refinement" ? "Refines tip via subtle rotation and projection. Alar base preserved."
              : state.preset === "Ethnic Preservation" ? "Light dorsal smoothing, identity-first. Nostrils untouched."
              : "Custom configuration. Adjust each axis independently."
            }
          </div>
        </div>
      )}

      {/* 03 WARP */}
      {session && (
        <div className="panel-section">
          <div className="section-label">
            <span><span className="num">03</span>Warp · {state.warping ? "computing…" : "live"}</span>
            <button className="btn-ghost btn" onClick={resetWarp}>Reset</button>
          </div>

          {PARAM_CATEGORIES.map((cat) => (
            <ParamCategory
              key={cat.name}
              cat={cat}
              warp={state.warp}
              view={view}
              updateWarp={updateWarp}
              open={!!openCats[cat.name]}
              onToggle={() => toggleCat(cat.name)}
            />
          ))}

          {state.warp.tipLift > 0.15 && (
            <div className="guardrail">
              <span className="icon">!</span>
              <div>
                Tip lift &gt; 0.15 takes the nose outside the harmonious range for your measured proportions.
                Consider 0.06–0.12 for a natural result.
              </div>
            </div>
          )}
        </div>
      )}

      {/* HEAL CTA */}
      {session && (
        <div className="heal-bar">
          <button
            className="heal-cta"
            disabled={!state.warpImageUrl || state.healing}
            onClick={runHeal}
          >
            <span>{state.healing ? "Generating…" : "Generate Photoreal Heal"}</span>
            <span className="est">~30s · 1 credit</span>
          </button>
          <details className="heal-options">
            <summary>Heal options</summary>
            <div className="opts">
              <Slider
                name="Strength"
                value={state.heal.strength}
                onChange={v => set({ heal: { ...state.heal, strength: v } })}
                harmony={[0.25, 0.5]}
                tooltip="How far the diffusion model deviates from the warped image."
              />
              <Slider
                name="Guidance"
                value={state.heal.guidance}
                onChange={v => set({ heal: { ...state.heal, guidance: v } })}
                min={1} max={10} step={0.1}
                harmony={[2.5, 4.5]}
                tooltip="Classifier-free guidance scale."
              />
              <Slider
                name="Denoise steps"
                value={state.heal.steps}
                onChange={v => set({ heal: { ...state.heal, steps: v } })}
                min={20} max={80} step={1}
                harmony={[28, 50]}
                tooltip="Denoising iterations per fal call. 28 is the server default."
              />
              <div style={{ marginTop: 12 }}>
                <div className="mono-tiny" style={{ marginBottom: 6 }}>Prompt</div>
                <textarea
                  value={state.healPrompt}
                  onChange={(e) => set({ healPrompt: e.target.value })}
                  style={{
                    width: "100%", minHeight: 60, resize: "vertical",
                    background: "var(--bg-card)", border: "1px solid var(--hairline)",
                    padding: 8, fontSize: 11, fontFamily: "var(--mono)", lineHeight: 1.5,
                    color: "var(--ink-2)",
                  }}
                />
              </div>
            </div>
          </details>
        </div>
      )}
    </div>
  );
};

Object.assign(window, { Slider, PreviewSlider, ParamCategory, PresetChips, DetectionRow, UploadZone, LeftPanel });
