/* Parley — the conversation. Commit model: INSTANT. There is no Save button on
   this screen; sending commits, stopping commits, attaching a file commits, and
   each control confirms itself. The two consequential ones — removing a file
   from the thread, changing who can read it — raise an undo toast from App.jsx.
   Deleting a thread is irreversible and opens a confirm dialog there.

   One data state per screen: /chats/:id is a thread with messages, / is a new
   chat with none. They are two routes and two components, never one screen
   showing both. */
(() => {
  const { Button, Textarea, Select, Badge, MenuItem, MenuGroupLabel, Separator, Kbd } = window.OriginDesignSystem_7c998b;

  /* Streaming is drawn by truncating the finished reply to the number of
     characters revealed so far — the same block renderer as a finished turn, so
     nothing reflows when it lands. */
  function sliceBlocks(blocks, chars) {
    const out = [];
    let left = chars;
    for (let i = 0; i < blocks.length && left > 0; i++) {
      const block = blocks[i];
      if (block.t === "ul" || block.t === "ol") {
        const items = [];
        for (let j = 0; j < block.items.length && left > 0; j++) {
          items.push(block.items[j].slice(0, left));
          left -= block.items[j].length;
        }
        out.push(Object.assign({}, block, { items: items }));
      } else if (block.t === "code") {
        out.push(Object.assign({}, block, { code: block.code.slice(0, left) }));
        left -= block.code.length;
      } else {
        out.push(Object.assign({}, block, { text: (block.text || "").slice(0, left) }));
        left -= (block.text || "").length;
      }
    }
    return out;
  }
  const blocksLength = (blocks) => blocks.reduce((n, b) => n + (b.t === "code" ? b.code.length : b.items ? b.items.join("").length : (b.text || "").length), 0);

  /* ── One turn ──────────────────────────────────────────────────────── */

  function Turn({ message, thread, data, streamChars, onRun, onFeedback, onNavigate }) {
    const now = data.now;
    if (message.role === "user") {
      return (
        <article className="pl-turn pl-turn--user">
          <div className="pl-bubble"><Prose blocks={message.blocks} /></div>
          <div className="pl-turn__byline"><span title={formatAbsolute(message.at)}>{formatWhen(message.at, now)}</span></div>
        </article>
      );
    }
    const streaming = message.status === "streaming";
    const blocks = streaming ? sliceBlocks(message.blocks, streamChars) : message.blocks;
    const transitions = messageTransitions(message);
    const cites = (message.cites || []).map((id) => fileById(data.files, id)).filter(Boolean);
    return (
      <article className="pl-turn">
        <div className="pl-turn__byline">
          <span className="pl-turn__mark" aria-hidden="true">P</span>
          <span className="pl-turn__model">{labelOf(MODELS, message.model || thread.model)}</span>
          <span title={formatAbsolute(message.at)}>{formatWhen(message.at, now)}</span>
          {message.status === "complete" ? null : <StatusBadge values={MESSAGE_STATUS} value={message.status} />}
        </div>
        {blocks.length ? <Prose blocks={blocks} caret={streaming} /> : null}
        {message.status === "failed" ? (
          <p className="pl-turn__note"><Icon name="triangle-alert" size={16} />{message.failedReason}</p>
        ) : null}
        {message.status === "stopped" && !streaming ? (
          <p className="pl-turn__note"><Icon name="square" size={16} />You stopped this at {formatClock(message.at)}. Continuing picks up from the last word rather than starting again.</p>
        ) : null}
        {cites.length ? (
          <div className="pl-cites">
            <Icon name="paperclip" size={13} />
            {cites.map((file) => (
              <button type="button" key={file.id} className="pl-link" onClick={() => onNavigate(resolvePath("/files/:id", { id: file.id }))}>{file.name}</button>
            ))}
          </div>
        ) : null}
        {streaming ? null : (
          <div className="pl-turn__foot">
            {transitions.map((t) => (
              <Button key={t.id} size="xs" variant={t.primary ? "outline" : "ghost"} onClick={() => onRun(message, t)}>
                {t.id === "copy" ? <Icon name="copy" size={13} /> : t.id === "branch" ? <Icon name="git-branch" size={13} /> : null}
                {t.label}
              </Button>
            ))}
            {message.status === "complete" ? (
              <span className="pl-turn__spacer">
                <FloatingTip side="bottom" label={message.feedback === "up" ? "Marked helpful" : "Helpful"}>
                  <Button size="icon-xs" variant={message.feedback === "up" ? "outline" : "ghost"} aria-pressed={message.feedback === "up"} aria-label="Mark this answer helpful" onClick={() => onFeedback(message, "up")}>
                    <Icon name="thumbs-up" size={13} />
                  </Button>
                </FloatingTip>
                <FloatingTip side="bottom" label={message.feedback === "down" ? "Marked not helpful" : "Not helpful"}>
                  <Button size="icon-xs" variant={message.feedback === "down" ? "outline" : "ghost"} aria-pressed={message.feedback === "down"} aria-label="Mark this answer not helpful" onClick={() => onFeedback(message, "down")}>
                    <Icon name="thumbs-down" size={13} />
                  </Button>
                </FloatingTip>
              </span>
            ) : null}
          </div>
        )}
      </article>
    );
  }

  /* ── The context rail ──────────────────────────────────────────────── */
  /* It carries the thread's anchor figure, so below 1280px it folds above the
     conversation rather than disappearing. */

  function ContextRail({ thread, data, onNavigate, onDetach }) {
    const [folded, setFolded] = React.useState(true);
    const model = modelOf(thread.model);
    const used = contextUsed(thread, data.files);
    const ratio = used / model.contextTokens;
    const near = THREAD_FLAGS.isNearLimit(thread, data.files);
    const full = THREAD_FLAGS.isFull(thread, data.files);
    const files = (thread.fileIds || []).map((id) => fileById(data.files, id)).filter(Boolean);
    const project = thread.projectId ? projectById(data.projects, thread.projectId) : null;
    const billing = threadBilling(thread, data.files);
    const owner = personById(data.people, thread.ownerId);
    return (
      <aside className="pl-rail" data-folded={folded} aria-label="What this thread is carrying">
        <button type="button" className="pl-rail__fold" onClick={() => setFolded(!folded)} aria-expanded={!folded}>
          <span className="pl-rail__label">Context · {formatTokens(used)} of {formatTokens(model.contextTokens)}{near ? " · running out" : ""}</span>
          <Icon name={folded ? "chevron-down" : "chevron-up"} size={16} />
        </button>
        <div className="pl-rail__panel">
          <div className="pl-rail__head">
            <span className="pl-rail__label">Context</span>
            <span className="pl-rail__label">{Math.round(ratio * 100)}%</span>
          </div>
          <span className="pl-rail__figure">{formatTokens(used)}</span>
          <Bar ratio={ratio} tone={near ? "warn" : null} />
          <p className="pl-rail__note">
            {full
              ? "This thread is " + formatTokens(used - model.contextTokens) + " past what " + model.label + " can hold, so it cannot take another turn. Switching to " + labelOf(MODELS, "corvus-m") + " keeps every message and every file."
              : near
                ? formatTokens(model.contextTokens - used) + " left on " + model.label + ". " + labelOf(MODELS, "corvus-m") + " has a " + formatTokens(modelOf("corvus-m").contextTokens) + " window — switch it in the composer and this thread keeps everything it has."
                : "of " + formatTokens(model.contextTokens) + " on " + model.label + ", counting " + thread.messages.length + " messages and " + files.filter(FILE_FLAGS.inContext).length + " indexed files."}
          </p>
        </div>

        <div className="pl-rail__panel">
          <div className="pl-rail__head">
            <span className="pl-rail__label">Attached</span>
            {files.length ? <span className="pl-rail__label">{files.filter(FILE_FLAGS.inContext).length} of {files.length} readable</span> : null}
          </div>
          {files.length ? (
            <div className="pl-rail__list">
              {files.map((file) => {
                const inContext = FILE_FLAGS.inContext(file);
                return (
                  <div className={"pl-rail__item" + (inContext ? "" : " pl-rail__item--out")} key={file.id}>
                    <FloatingTip side="bottom" label={inContext ? formatTokens(file.tokens) + " tokens" : statusValue(FILE_STATUS, file.status).label + " — " + (file.statusNote || "")}>
                      <span className="fd-icon"><Icon name={inContext ? "file-text" : file.status === "indexing" ? "loader" : "file-x"} size={14} /></span>
                    </FloatingTip>
                    <button type="button" className="pl-rail__item-name pl-link" onClick={() => onNavigate(resolvePath("/files/:id", { id: file.id }))}>{file.name}</button>
                    <Button size="icon-xs" variant="ghost" aria-label={"Remove " + file.name + " from this thread"} onClick={() => onDetach(file)}><Icon name="x" size={13} /></Button>
                  </div>
                );
              })}
            </div>
          ) : (
            <p className="pl-rail__note">Nothing attached. Files added here are read on every turn, so they count against the window above.</p>
          )}
          {project ? (
            <>
              <Separator />
              <div className="pl-rail__head">
                <span className="pl-rail__label">Project</span>
              </div>
              <button type="button" className="pl-link" onClick={() => onNavigate(resolvePath("/projects/:id", { id: project.id }))}>{project.name}</button>
              <p className="pl-rail__note">Its instructions are sent ahead of every turn in this thread.</p>
            </>
          ) : null}
        </div>

        <div className="pl-rail__panel">
          <div className="pl-rail__head"><span className="pl-rail__label">This thread</span></div>
          <dl className="pl-defs">
            <div><dt>Started</dt><dd title={formatAbsolute(thread.createdAt)}>{formatDay(thread.createdAt)}</dd></div>
            <div><dt>Owner</dt><dd>{owner.name}</dd></div>
            <div><dt>Billed turns</dt><dd>{billing.rows.length}</dd></div>
            <div><dt>Cost</dt><dd>{formatCost(billing.cost)}</dd></div>
          </dl>
          <p className="pl-rail__note">Every turn re-reads the whole thread, so the cost grows with the window above, not with what you type.</p>
        </div>
      </aside>
    );
  }

  /* ── The composer ──────────────────────────────────────────────────── */

  function Composer({ thread, data, value, onChange, onSend, onStop, streaming, onModelChange, onAttach, onUsePrompt, autoFocus }) {
    const model = modelOf(thread.model);
    const near = THREAD_FLAGS.isNearLimit(thread, data.files);
    const full = THREAD_FLAGS.isFull(thread, data.files);
    const attachable = data.files.filter((f) => (thread.fileIds || []).indexOf(f.id) === -1);
    const ready = value.trim().length > 0 && !full;
    const submit = () => { if (ready && !streaming) onSend(value.trim()); };
    const onKeyDown = (e) => {
      if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); submit(); }
    };
    return (
      <div className="pl-composer">
        {near ? (
          <p className="pl-turn__note" style={{ marginBottom: "var(--space-2)" }}>
            <Icon name="gauge" size={16} />
            {full
              ? model.label + " cannot take another turn on this thread — it is " + formatTokens(contextUsed(thread, data.files) - model.contextTokens) + " past its " + formatTokens(model.contextTokens) + " window. Switch to " + labelOf(MODELS, "corvus-m") + " and it keeps every message and every file."
              : model.label + " has " + formatTokens(contextLeft(thread, data.files)) + " of its window left. Switch this thread to " + labelOf(MODELS, "corvus-m") + " and it keeps every message it already has."}
          </p>
        ) : null}
        <div className="pl-composer__box">
          <Textarea rows={2} value={value} autoFocus={autoFocus} aria-label="Message Parley" onKeyDown={onKeyDown}
            placeholder={thread.messages.length ? "Reply, or ask for the next thing" : "Ask anything, or attach the file it is about"}
            onChange={(e) => onChange(e.target.value)} />
          <div className="pl-composer__bar">
            <span className="pl-composer__model">
              <label className="pl-sr" htmlFor="pl-model">Model</label>
              <Select id="pl-model" size="sm" items={MODELS.map((m) => ({ value: m.value, label: m.label }))} value={thread.model} onValueChange={onModelChange} />
            </span>
            <Floating align="start" width="19rem" label="Attach a file"
              trigger={<Button variant="ghost" size="sm" aria-label="Attach a file"><Icon name="paperclip" size={14} />Attach</Button>}>
              {(close) => (
                <>
                  <MenuGroupLabel>Files in {WORKSPACE.name}</MenuGroupLabel>
                  {attachable.length
                    ? attachable.map((file) => (
                      <MenuItem key={file.id} disabled={!FILE_FLAGS.inContext(file)} onClick={() => { onAttach(file); close(); }}>
                        {file.name}{FILE_FLAGS.inContext(file) ? " · " + formatTokens(file.tokens) : " · " + statusValue(FILE_STATUS, file.status).label.toLowerCase()}
                      </MenuItem>
                    ))
                    : <p className="pl-menu-note">Everything in the workspace is already on this thread.</p>}
                </>
              )}
            </Floating>
            <Floating align="start" width="21rem" label="Saved prompts"
              trigger={<Button variant="ghost" size="sm" aria-label="Use a saved prompt"><Icon name="bookmark" size={14} />Prompts</Button>}>
              {(close) => (
                <>
                  <MenuGroupLabel>Most used</MenuGroupLabel>
                  {data.prompts.slice().sort((a, b) => b.uses - a.uses).slice(0, 4).map((prompt) => (
                    <MenuItem key={prompt.id} onClick={() => { onUsePrompt(prompt); close(); }}>{prompt.title}</MenuItem>
                  ))}
                </>
              )}
            </Floating>
            <span className="pl-composer__bar-end">
              {streaming
                ? <Button size="sm" variant="outline" onClick={onStop}><Icon name="square" size={14} />Stop</Button>
                : <Button size="sm" disabled={!ready} onClick={submit}>Send<Icon name="arrow-up" size={14} /></Button>}
            </span>
          </div>
        </div>
        <div className="pl-composer__foot">
          <span>{formatTokens(contextUsed(thread, data.files) + estimateTokens(value))} of {formatTokens(model.contextTokens)} in context</span>
          <span><Kbd>↵</Kbd> send · <Kbd>⇧↵</Kbd> new line</span>
        </div>
      </div>
    );
  }

  /* ── The thread ────────────────────────────────────────────────────── */

  function ThreadScreen({ thread, data, user, draft, onDraft, onSend, onStop, streamChars, onRunThread, onRunMessage, onFeedback, onModelChange, onAttach, onDetach, onNavigate }) {
    const project = thread.projectId ? projectById(data.projects, thread.projectId) : null;
    const owner = personById(data.people, thread.ownerId);
    const mine = thread.ownerId === user.id;
    const archived = THREAD_FLAGS.isArchived(thread);
    const transitions = threadTransitions(thread);
    const streaming = thread.messages.some((m) => m.status === "streaming");
    React.useEffect(() => {
      const box = document.querySelector(".pl-inset");
      if (box) box.scrollTop = box.scrollHeight;
    }, [thread.id, thread.messages.length]);

    return (
      <div className="pl-chat">
        <div className="pl-chat__main">
          <header className="pl-chat__head">
            <div className="pl-chat__head-text">
              <h1 className="pl-chat__title">{thread.title}</h1>
              <span className="pl-chat__meta">
                {project ? <button type="button" className="pl-link" onClick={() => onNavigate(resolvePath("/projects/:id", { id: project.id }))}>{project.name}</button> : "No project"}
                <span>·</span>
                <span>{mine ? "Yours" : owner.name}</span>
                <span>·</span>
                <span title={formatAbsolute(THREAD_FLAGS.lastActivityAt(thread))}>{formatAge(THREAD_FLAGS.lastActivityAt(thread), data.now)}</span>
              </span>
            </div>
            <div className="pl-chat__actions">
              {archived ? <Badge size="sm" variant="secondary">Archived</Badge> : <VisibilityBadge value={thread.visibility} />}
              {/* Restoring is the one thing an archived thread offers, and it is
                  offered by the bar that replaced the composer — so the header
                  keeps only the rest. */}
              <RowActions transitions={archived ? transitions.filter((t) => t.id !== "restore") : transitions} onRun={(t) => onRunThread(thread, t)} label={"Actions for " + thread.title} />
            </div>
          </header>

          <div className="pl-stream">
            {thread.visibility === "link" ? (
              <p className="pl-turn__note"><Icon name="link" size={16} />{thread.linkNote}</p>
            ) : null}
            {thread.messages.map((message) => (
              <Turn key={message.id} message={message} thread={thread} data={data} streamChars={streamChars}
                onRun={onRunMessage} onFeedback={onFeedback} onNavigate={onNavigate} />
            ))}
          </div>

          {archived ? (
            <div className="pl-composer">
              <div className="pl-readonly">
                <span>
                  Archived {formatAge(thread.archivedAt, data.now)}. Every turn and every attachment is still here; it cannot take a new one until it is restored.
                </span>
                <span className="pl-readonly__actions">
                  <Button size="sm" onClick={() => onRunThread(thread, { id: "restore", label: "Restore" })}>Restore</Button>
                </span>
              </div>
            </div>
          ) : mine ? (
            <Composer thread={thread} data={data} value={draft} onChange={onDraft} onSend={onSend} onStop={onStop} streaming={streaming}
              onModelChange={onModelChange} onAttach={onAttach} onUsePrompt={(p) => onDraft(p.body)} />
          ) : (
            <div className="pl-composer">
              <div className="pl-readonly">
                <span>This is {owner.name.split(" ")[0]}&rsquo;s thread, shared with the workspace. Branching copies everything above into a thread of your own — nothing you write lands here.</span>
                <span className="pl-readonly__actions">
                  <Button size="sm" onClick={() => onRunMessage(THREAD_FLAGS.lastMessage(thread), { id: "branch", label: "Branch from here" })}>Branch from here</Button>
                </span>
              </div>
            </div>
          )}
        </div>
        <ContextRail thread={thread} data={data} onNavigate={onNavigate} onDetach={onDetach} />
      </div>
    );
  }

  /* ── The new chat ──────────────────────────────────────────────────── */

  function NewChatScreen({ draft, data, user, model, onDraft, onModelChange, onSend, onAttach, onNavigate }) {
    const hour = new Date(data.now).getHours();
    const part = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
    /* A blank thread stands in for the record the composer needs: same model,
       same context arithmetic, no messages and nothing attached yet. */
    const blank = { id: "new", title: "New chat", model: model, messages: [], fileIds: [], ownerId: user.id, visibility: "private", createdAt: data.now };
    const starters = data.prompts.slice().sort((a, b) => b.uses - a.uses).slice(0, 3);
    return (
      <div className="pl-open">
        <div>
          <h1 className="pl-open__greeting">{part}, {user.name.split(" ")[0]}.</h1>
          <p className="pl-open__note">
            {WORKSPACE.name} has {data.files.filter(FILE_FLAGS.inContext).length} indexed files and {data.projects.filter((p) => !PROJECT_FLAGS.isArchived(p)).length} live projects. Attach what this is about and it gets read on every turn.
          </p>
        </div>
        <Composer thread={blank} data={data} value={draft} onChange={onDraft} onSend={onSend} onStop={() => {}} streaming={false}
          onModelChange={onModelChange} onAttach={onAttach} onUsePrompt={(p) => onDraft(p.body)} autoFocus />
        <div>
          <p className="pl-rail__label" style={{ paddingBottom: "var(--space-2)" }}>Your most used prompts</p>
          <div className="pl-starters">
            {starters.map((prompt) => (
              <button type="button" className="pl-starter" key={prompt.id} onClick={() => onDraft(prompt.body)}>
                <span className="pl-starter__title">{prompt.title}</span>
                <span className="pl-starter__body">{prompt.body}</span>
              </button>
            ))}
          </div>
        </div>
      </div>
    );
  }

  Object.assign(window, { ThreadScreen, NewChatScreen, ContextRail, Composer, Turn, sliceBlocks, blocksLength });
})();
