// ====== Skill editor (with file tree) ======

function TreeNode({ node, depth, active, expanded, onToggleExpand, onSelect, onDelete }) {
  const indentCls = depth > 0 ? `indent-${Math.min(depth, 2)}` : "";
  if (node.isDir) {
    const open = expanded.has(node.path);
    return (
      <>
        <div
          className={`tree-row dir ${indentCls} ${open ? "open" : ""}`}
          onClick={() => onToggleExpand(node.path)}
          title={node.path}
        >
          <span className="chev"><Icon name="chev" size={10} /></span>
          <span className="ic">📁</span>
          <span>{node.name}/</span>
        </div>
        {open && node.children.map((c) => (
          <TreeNode key={c.path} node={c} depth={depth + 1}
            active={active} expanded={expanded}
            onToggleExpand={onToggleExpand} onSelect={onSelect} onDelete={onDelete}
          />
        ))}
      </>
    );
  }
  const isReq = node.file.required;
  return (
    <div
      className={`tree-row ${indentCls} ${active === node.path ? "active" : ""}`}
      onClick={() => onSelect(node.path)}
      title={node.path}
    >
      <span className="chev" style={{ visibility: "hidden" }}>·</span>
      <span className="ic"><Icon name="code" size={11} /></span>
      <span>{node.name}</span>
      {isReq && <span className="req">REQ</span>}
      {!isReq && (
        <span className="x" onClick={(e) => { e.stopPropagation(); onDelete(node.path); }} title="Delete">
          <Icon name="x" size={11} />
        </span>
      )}
    </div>
  );
}

function TextEditor({ file, disabled, onChange, changedRange }) {
  const textareaRef = React.useRef(null);
  const gutterRef = React.useRef(null);
  const backdropRef = React.useRef(null);
  const lines = (file.content || "").split("\n");
  const highlightLine = (lineNumber) => changedRange
    && lineNumber >= changedRange.start && lineNumber <= changedRange.end;
  const syncScroll = (event) => {
    const { scrollTop, scrollLeft } = event.currentTarget;
    if (gutterRef.current) gutterRef.current.scrollTop = scrollTop;
    if (backdropRef.current) {
      backdropRef.current.scrollTop = scrollTop;
      backdropRef.current.scrollLeft = scrollLeft;
    }
  };
  return <div className="code-editor-surface">
    <div className="code-gutter" ref={gutterRef} aria-hidden="true">
      {lines.map((_, index) => <span className={highlightLine(index + 1) ? "changed" : ""} key={index}>{index + 1}</span>)}
    </div>
    <pre className="code-backdrop" ref={backdropRef} aria-hidden="true">{lines.map((line, index) => <span className={highlightLine(index + 1) ? "changed" : ""} key={index}>{line || " "}</span>)}</pre>
    <textarea
      ref={textareaRef}
      key={file.path}
      className="code-area"
      value={file.content}
      disabled={disabled}
      spellCheck={false}
      wrap="off"
      onScroll={syncScroll}
      onChange={(e) => onChange(e.target.value)}
    />
  </div>;
}

function SkillEditor({
  skill, onUpdate, onDelete, onDuplicate, onPublishVersion,
  onPublishToMarketplace, onWithdrawFromMarketplace, saveState, onSave,
  testState, onTest, onApplyTestProposal, changedLines,
}) {
  const [activePath, setActivePath] = React.useState(skill.files[0]?.path || "SKILL.md");
  const [expanded, setExpanded] = React.useState(new Set(["scripts", "references", "assets"]));
  const [newPath, setNewPath] = React.useState("");
  const [showNew, setShowNew] = React.useState(false);

  // Keep activePath valid when switching skill
  React.useEffect(() => {
    if (!skill.files.find((f) => f.path === activePath)) {
      setActivePath(skill.files[0]?.path || "SKILL.md");
    }
  }, [skill.id]);

  const tree = React.useMemo(() => buildTree(skill.files), [skill.files]);
  const activeFile = skill.files.find((f) => f.path === activePath);
  const testedAt = Number(testState?.completed_at || 0);
  const testPassedLabel = testState?.status === "passed"
    ? `Skill Test passed${testedAt ? ` · ${new Date(testedAt).toLocaleString()}` : ""}`
    : null;

  const updateFile = (path, patch) => {
    if (!skill.editable) return;
    onUpdate({
      files: skill.files.map((f) => f.path === path ? { ...f, ...patch } : f),
    });
  };
  const deleteFile = (path) => {
    if (!skill.editable) return;
    if (skill.files.find((f) => f.path === path)?.required) return;
    if (!confirm(`Delete ${path}?`)) return;
    const next = skill.files.filter((f) => f.path !== path);
    onUpdate({ files: next });
    if (activePath === path) setActivePath(next[0]?.path || "SKILL.md");
  };
  const addFile = () => {
    if (!skill.editable) return;
    const p = newPath.trim();
    if (!p) return;
    if (skill.files.some((f) => f.path === p)) { alert("File already exists"); return; }
    onUpdate({ files: [...skill.files, { path: p, content: "" }] });
    setActivePath(p);
    setNewPath("");
    setShowNew(false);
    // ensure parent dir expanded
    const parent = p.includes("/") ? p.split("/").slice(0, -1).join("/") : null;
    if (parent) setExpanded((s) => new Set([...s, parent]));
  };
  const toggleExpand = (path) => {
    setExpanded((s) => {
      const next = new Set(s);
      if (next.has(path)) next.delete(path); else next.add(path);
      return next;
    });
  };

  return (
    <div className="detail">
      <div className="detail-head">
        <div className="av" style={{ background: skill.color }}>{skill.initials}</div>
        <div className="titles">
          <h1>{skill.name}</h1>
          <div className="sub">skill · <span style={{ color: "var(--text)" }}>{skill.id}</span> · v{skill.version === "draft" ? "0.1.0" : (skill.version || "0.1.0")} · {skill.files.length} files{skill.marketplace_visibility ? ` · Marketplace: ${skill.marketplace_visibility}` : ""}</div>
          {testPassedLabel && <div className="skill-test-passed"><Icon name="check" size={12} /> {testPassedLabel}</div>}
        </div>
        <div className="actions">
          {skill.editable && skill.marketplace_visibility === "public" && onWithdrawFromMarketplace ? (
            <button className="btn" onClick={onWithdrawFromMarketplace} title="Remove this skill from Marketplace; existing installations keep their version">
              <Icon name="x" size={12} /> Remove from Marketplace
            </button>
          ) : skill.editable && onPublishToMarketplace ? (
            <button className="btn primary" onClick={onPublishToMarketplace} disabled={!skill.latest_version} title={skill.latest_version ? "Make the latest published version available in Marketplace" : "Publish a version before listing it in Marketplace"}>
              <Icon name="upload" size={12} /> Publish to Marketplace
            </button>
          ) : null}
          {skill.editable && <button className="btn danger" onClick={onDelete}><Icon name="trash" size={12} /> Delete</button>}
        </div>
      </div>

      <div className="detail-body wide">
        {!skill.editable && (
          <div style={{
            padding: "10px 14px", margin: "0 0 12px",
            background: "var(--bg-subtle)", borderRadius: 6,
            border: "1px solid var(--border)",
            color: "var(--text-muted)", fontSize: 12,
          }}>
            This is a <b>public skill</b> — installed from the platform catalog, not owned by your tenant.
            Read-only here; you can't edit, delete, or publish new versions.
          </div>
        )}
        <div className="form-grid">
          <div className="card-section">
            <div className="head"><h3>Metadata</h3></div>
            <div className="body padded" style={{ display: "grid", gridTemplateColumns: "1fr 120px", gap: 14 }}>
              <div className="field-lg">
                <label>Skill name <span className="hint">— used as directory name</span></label>
                <input
                  className="input-lg"
                  style={{ fontFamily: "var(--font-mono)" }}
                  value={skill.name}
                  disabled={!skill.editable}
                  onChange={(e) => {
                    const next = e.target.value.replace(/[^a-z0-9_]/gi, "_").toLowerCase();
                    // 同步改 SKILL.md frontmatter 的 name: —— 不然只改本地 skill.name 显示,
                    // 后端 record.name 不变(PATCH 还在送旧 SKILL.md);user 看着像改了刷页就变回去。
                    // backend 现在允许 unpublished draft 改名;不会再因这一行触发 422。
                    const patch = { name: next };
                    const md = (skill.files || []).find((f) => f.path === "SKILL.md");
                    if (md && md.content) {
                      const rewritten = md.content.replace(/^(name:\s*).+$/m, `$1${next}`);
                      if (rewritten !== md.content) {
                        patch.files = (skill.files || []).map((f) =>
                          f.path === "SKILL.md" ? { ...f, content: rewritten } : f);
                      }
                    }
                    onUpdate(patch);
                  }}
                />
              </div>
              <div className="field-lg">
                <label>Version</label>
                <input
                  className="input-lg"
                  style={{ fontFamily: "var(--font-mono)" }}
                  value={skill.version === "draft" ? "0.1.0" : (skill.version || "0.1.0")}
                  disabled={!skill.editable}
                  onChange={(e) => onUpdate({ version: e.target.value })}
                />
              </div>
              <div className="field-lg" style={{ gridColumn: "1 / -1" }}>
                <label>Description</label>
                <input
                  className="input-lg"
                  value={skill.description}
                  disabled={!skill.editable}
                  onChange={(e) => {
                    const next = e.target.value;
                    const patch = { description: next };
                    const md = (skill.files || []).find((f) => f.path === "SKILL.md");
                    if (md && md.content) {
                      const rewritten = md.content.replace(/^(description:\s*).+$/m, `$1${next}`);
                      if (rewritten !== md.content) {
                        patch.files = (skill.files || []).map((f) =>
                          f.path === "SKILL.md" ? { ...f, content: rewritten } : f);
                      }
                    }
                    onUpdate(patch);
                  }}
                />
              </div>
            </div>
          </div>

          <div className="card-section" style={{ overflow: "hidden" }}>
            <div className="head">
              <h3>Files</h3>
              <span className="desc">SKILL.md required · scripts / references / assets are conventional</span>
            </div>
            <div className="body flat">
              <div className="skill-editor">
                <div className="tree">
                  <div className="tree-head">
                    <Icon name="code" size={12} />
                    <span className="nm">{skill.name}/</span>
                  </div>
                  <div className="tree-list">
                    {tree.children.map((c) => (
                      <TreeNode
                        key={c.path}
                        node={c}
                        depth={0}
                        active={activePath}
                        expanded={expanded}
                        onToggleExpand={toggleExpand}
                        onSelect={setActivePath}
                        onDelete={deleteFile}
                      />
                    ))}
                    {showNew && (
                      <div style={{ padding: "4px 6px" }}>
                        <input
                          autoFocus
                          className="input-lg"
                          style={{ height: 26, padding: "0 6px", fontFamily: "var(--font-mono)", fontSize: 12 }}
                          placeholder="e.g. scripts/new.py"
                          value={newPath}
                          disabled={!skill.editable}
                          onChange={(e) => setNewPath(e.target.value)}
                          onKeyDown={(e) => {
                            if (e.key === "Enter") addFile();
                            if (e.key === "Escape") { setShowNew(false); setNewPath(""); }
                          }}
                        />
                      </div>
                    )}
                  </div>
                  <div className="tree-actions">
                    <button className="btn sm" disabled={!skill.editable} onClick={() => setShowNew((v) => !v)}>
                      <Icon name="plus" size={11} /> New file
                    </button>
                  </div>
                </div>

                <div className="file-pane">
                  {activeFile ? (
                    <>
                      <div className="file-head">
                        <span className="path">{skill.name}/{activeFile.path}</span>
                        <span className="lang">{langFor(activeFile.path)}</span>
                        <div className="spacer" />
                        {activeFile.required && <span className="tag" style={{ background: "var(--accent-bg)", color: "var(--accent)", fontFamily: "var(--font-mono)", fontSize: 10.5, padding: "2px 6px", borderRadius: 4 }}>REQUIRED</span>}
                      </div>
                      <TextEditor
                        file={activeFile}
                        disabled={!skill.editable}
                        changedRange={changedLines?.[activeFile.path]}
                        onChange={(content) => updateFile(activeFile.path, { content })}
                      />
                    </>
                  ) : (
                    <div className="empty">
                      <p>Select a file to edit</p>
                    </div>
                  )}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      {skill.editable && testState && testState.status !== "idle" && (
        <div className="skill-test-dock">
          <SkillTestReport state={testState} onApply={onApplyTestProposal} />
        </div>
      )}
      <SaveBar
        saveState={saveState}
        onSave={onSave}
        extraButtons={skill.editable ? <>
          <button
            className="btn"
            onClick={onTest}
            disabled={saveState?.status === "dirty" || saveState?.status === "saving" || testState?.status === "running"}
            title="Run AI quality and isolated runtime checks against the saved draft"
          >
            <Icon name="play" size={12} /> {testState?.status === "running" ? "Testing…" : "Test"}
          </button>
          {onPublishVersion && <button
            className="btn"
            onClick={onPublishVersion}
            disabled={saveState?.status === "dirty" || saveState?.status === "saving"}
            title={testState?.status === "passed" ? "Create a private immutable version from the tested draft" : "Create a private immutable version; Skill Test results are advisory"}
          >
            <Icon name="upload" size={12} /> Publish version
          </button>}
        </> : null}
      />
    </div>
  );
}

function SkillTestReport({ state, onApply }) {
  const [applyingProposalId, setApplyingProposalId] = React.useState(null);
  const quality = state.quality || {};
  const runtime = state.runtime || {};
  const stateLabel = state.applying_proposal_id ? "Applying" : state.applied_proposal ? "Applied" : state.status === "passed" ? "Passed" : state.status === "running" ? "Running" : state.status === "stale" ? "Stale" : "Failed";
  const findings = quality.findings || [];
  const proposals = quality.proposals || [];
  const inProgress = state.status === "running";
  const stageDetail = state.stage === "runtime"
    ? "Runtime smoke test is running in an isolated workspace."
    : "AI is checking the saved Skill against platform rules.";
  const apply = async (proposalId) => {
    setApplyingProposalId(proposalId);
    try { await onApply(proposalId); } finally { setApplyingProposalId(null); }
  };
  return <div className={`card-section skill-test-report ${state.status} ${inProgress ? "in-progress" : ""}`}>
    <div className="head"><h3>Skill Test <span className="tag">{stateLabel}</span></h3>{inProgress && <span className="skill-test-live"><span className="dot" /> {stageDetail}</span>}</div>
    <div className="body">
      {state.applying_proposal_id && <div className="skill-test-status applying"><span className="dot" /> Applying the suggested change and saving the draft...</div>}
      {state.applied_proposal && <div className="skill-test-status applied">Applied {state.applied_proposal.path || "the suggested change"} and saved the draft. This report is now stale; run Test again before publishing.</div>}
      <div className="skill-test-stage"><b>1. AI quality</b><span>{quality.status || "pending"}</span></div>
      {!inProgress && findings.map((item, index) => <div className="skill-test-finding" key={index}>{item.path || "SKILL.md"}: {item.message || String(item)}</div>)}
      {!state.applied_proposal && proposals.map((proposal) => <div className="skill-test-proposal" key={proposal.id}>
        <span>{proposal.path}: {proposal.reason}</span>
        <button className="btn sm" disabled={!!applyingProposalId || !!state.applying_proposal_id} onClick={() => apply(proposal.id)}>
          <Icon name="check" size={11} /> {applyingProposalId === proposal.id ? "Applying…" : "Apply"}
        </button>
      </div>)}
      <div className="skill-test-stage"><b>2. Real runtime</b><span>{runtime.status || "pending"}</span></div>
      {!inProgress && runtime.error && <div className="skill-test-finding">{runtime.error}</div>}
      {!inProgress && runtime.output_text && <pre className="skill-test-output">{runtime.output_text}</pre>}
    </div>
  </div>;
}

window.SkillEditor = SkillEditor;
