// chat.jsx
// Chat sidebar: thread + composer with optional quoted selection.

const { useState, useRef, useEffect, useCallback } = React;

const Icon = {
  Send: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 19V5M5 12l7-7 7 7" />
    </svg>
  ),
  Plus: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 5v14M5 12h14" />
    </svg>
  ),
  Paperclip: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 11l-8.5 8.5a5 5 0 0 1-7-7L13 4a3.5 3.5 0 0 1 5 5l-8.5 8.5a2 2 0 0 1-3-3L15 6" />
    </svg>
  ),
  Quote: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M7 7h4v6H7zM13 7h4v6h-4z" />
      <path d="M9 13l-2 4M15 13l-2 4" />
    </svg>
  ),
  Sparkle: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1" />
    </svg>
  ),
  FileText: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
      <path d="M14 3v5h5M9 13h6M9 17h4" />
    </svg>
  ),
  FileCode: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
      <path d="M14 3v5h5" />
      <path d="M10 13l-2 2 2 2M14 13l2 2-2 2" />
    </svg>
  ),
  FileImage: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
      <path d="M14 3v5h5" />
      <circle cx="10" cy="14" r="1.5" />
      <path d="M19 18l-4-4-6 5" />
    </svg>
  ),
  File: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
      <path d="M14 3v5h5" />
    </svg>
  ),
};

// ─── File reading helpers ─────────────────────────────────────
const TEXT_EXTENSIONS = new Set([
  'md', 'mdx', 'markdown', 'txt', 'text', 'log',
  'json', 'jsonc', 'yml', 'yaml', 'toml', 'ini', 'env', 'csv', 'tsv',
  'xml', 'html', 'htm', 'svg',
  'css', 'scss', 'sass', 'less',
  'js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx',
  'py', 'rb', 'go', 'rs', 'java', 'kt', 'swift', 'c', 'cc', 'cpp', 'h', 'hpp',
  'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat',
  'sql', 'graphql', 'gql', 'proto', 'dockerfile', 'gitignore', 'editorconfig',
  'rtf',
]);

function getExt(name) {
  const i = (name || '').lastIndexOf('.');
  return i >= 0 ? name.slice(i + 1).toLowerCase() : '';
}

function isTextLike(file) {
  if (file.type.startsWith('text/')) return true;
  if (file.type === 'application/json' || file.type === 'application/xml' || file.type === 'application/javascript') return true;
  return TEXT_EXTENSIONS.has(getExt(file.name));
}

function isImage(file) {
  return file.type.startsWith('image/');
}

function isDocx(file) {
  return file.name.toLowerCase().endsWith('.docx') ||
         file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}

function isPdf(file) {
  return file.name.toLowerCase().endsWith('.pdf') || file.type === 'application/pdf';
}

function readAsText(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onerror = () => reject(new Error('read failed'));
    r.onload = () => resolve(String(r.result || ''));
    r.readAsText(file);
  });
}

async function readDocx(file) {
  if (typeof JSZip === 'undefined') throw new Error('JSZip not loaded');
  const buf = await file.arrayBuffer();
  const zip = await JSZip.loadAsync(buf);
  const xmlFile = zip.file('word/document.xml');
  if (!xmlFile) throw new Error('not a valid docx (no word/document.xml)');
  const xml = await xmlFile.async('text');
  const doc = new DOMParser().parseFromString(xml, 'application/xml');
  const paras = doc.getElementsByTagName('w:p');
  const lines = [];
  for (let i = 0; i < paras.length; i++) {
    const p = paras[i];
    const texts = p.getElementsByTagName('w:t');
    let line = '';
    for (let j = 0; j < texts.length; j++) line += texts[j].textContent || '';
    // Detect heading style for nicer markdown
    const styles = p.getElementsByTagName('w:pStyle');
    const styleVal = styles.length ? (styles[0].getAttribute('w:val') || '') : '';
    if (/heading1/i.test(styleVal)) lines.push('# ' + line);
    else if (/heading2/i.test(styleVal)) lines.push('## ' + line);
    else if (/heading3/i.test(styleVal)) lines.push('### ' + line);
    else lines.push(line);
  }
  return lines.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
}

const MAX_CONTENT_CHARS = 60000;

async function loadFile(file) {
  const base = {
    id: 'f-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7),
    name: file.name,
    size: file.size,
    type: file.type || ('.' + getExt(file.name)),
  };
  try {
    if (isImage(file)) {
      return { ...base, kind: 'image', content: null, note: 'Image attached (chat is text-only, the file content cannot be read).' };
    }
    if (isPdf(file)) {
      return { ...base, kind: 'pdf', content: null, note: 'PDF attached — content cannot be parsed in-browser yet. Paste key text instead, or attach a DOCX/MD.' };
    }
    if (isDocx(file)) {
      const text = await readDocx(file);
      const truncated = text.length > MAX_CONTENT_CHARS;
      return { ...base, kind: 'docx', content: truncated ? text.slice(0, MAX_CONTENT_CHARS) : text, truncated };
    }
    if (isTextLike(file)) {
      const text = await readAsText(file);
      const truncated = text.length > MAX_CONTENT_CHARS;
      return { ...base, kind: 'text', content: truncated ? text.slice(0, MAX_CONTENT_CHARS) : text, truncated };
    }
    return { ...base, kind: 'unknown', content: null, note: 'Unsupported file format — only metadata will be shared.' };
  } catch (err) {
    return { ...base, kind: 'error', error: err.message || 'Failed to read', content: null };
  }
}

function fileIcon(file) {
  if (file.kind === 'image') return <Icon.FileImage />;
  if (['js','jsx','ts','tsx','py','rb','go','rs','sh','json','yaml','yml','toml','xml','html','css','scss'].includes(getExt(file.name))) {
    return <Icon.FileCode />;
  }
  if (file.kind === 'text' || file.kind === 'docx' || file.kind === 'pdf') return <Icon.FileText />;
  return <Icon.File />;
}

function formatBytes(n) {
  if (n == null) return '';
  if (n < 1024) return n + ' B';
  if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
  return (n / 1024 / 1024).toFixed(1) + ' MB';
}

function FileChip({ file, onRemove }) {
  const sub = file.kind === 'error'
    ? file.error
    : (file.kind === 'pdf' || file.kind === 'image' || file.kind === 'unknown')
      ? file.kind.toUpperCase()
      : (file.truncated ? formatBytes(file.size) + ' · trimmed' : formatBytes(file.size));
  return (
    <div className={`file-chip ${file.kind === 'error' ? 'error' : ''} ${file.loading ? 'loading' : ''}`} title={file.note || file.error || file.name}>
      <div className="ficon">{fileIcon(file)}</div>
      <div className="finfo">
        <span className="fname">{file.name}</span>
        <span className="fmeta">{sub}</span>
      </div>
      {onRemove ? <button className="fx" onClick={() => onRemove(file.id)} aria-label="Remove">×</button> : null}
    </div>
  );
}

function Thinking() {
  return (
    <div className="thinking" aria-label="Claude is thinking">
      <span></span><span></span><span></span>
    </div>
  );
}

function MessageView({ msg, onJumpToDoc }) {
  if (msg.role === 'user') {
    return (
      <div className="msg user">
        {msg.files && msg.files.length ? (
          <div className="files-attached">
            {msg.files.map((f) => (
              <FileChip key={f.id} file={f} />
            ))}
          </div>
        ) : null}
        {msg.quote ? (
          <div className="quote-attached">{truncate(msg.quote, 240)}</div>
        ) : null}
        <div className="bubble">{msg.content}</div>
      </div>
    );
  }
  return (
    <div className="msg assistant">
      <div className="who">Claude</div>
      <div className="body">
        {msg.content}
        {msg.thinking ? <Thinking /> : null}
      </div>
      {msg.docAction ? (
        <div className="doc-action">
          <span className="dot"></span>
          <span>
            {msg.docAction.kind === 'created' && 'Created'}
            {msg.docAction.kind === 'updated' && 'Updated'}
            {msg.docAction.kind === 'rewrote' && 'Rewrote section in'}
            {' '}<a onClick={onJumpToDoc}>{msg.docAction.title || 'document'}</a>
            {msg.docAction.added != null && (
              <span style={{ marginLeft: 6, color: 'var(--ink-faint)' }}>
                +{msg.docAction.added} −{msg.docAction.removed}
              </span>
            )}
          </span>
        </div>
      ) : null}
    </div>
  );
}

function truncate(s, n) {
  if (!s) return '';
  if (s.length <= n) return s;
  return s.slice(0, n - 1).trim() + '…';
}

const STARTER_PROMPTS = [
  "Draft a PRD for an AI-powered onboarding checklist",
  "One-pager: Q3 strategy bets for our growth team",
  "Launch comms for a new dashboard, 3 audience tiers",
  "RFC: migrating from REST to event-driven for billing",
];

function Chat({
  messages,
  pendingQuote,
  setPendingQuote,
  pendingFiles,
  setPendingFiles,
  isStreaming,
  onSend,
  onNewChat,
  onJumpToDoc,
  docExists,
}) {
  const [draft, setDraft] = useState('');
  const [dragging, setDragging] = useState(false);
  const taRef = useRef(null);
  const threadRef = useRef(null);
  const fileInputRef = useRef(null);

  // Auto-resize textarea
  useEffect(() => {
    const ta = taRef.current;
    if (!ta) return;
    ta.style.height = 'auto';
    ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
  }, [draft, pendingQuote, pendingFiles]);

  // Auto-scroll thread on new messages
  useEffect(() => {
    const t = threadRef.current;
    if (!t) return;
    t.scrollTo({ top: t.scrollHeight, behavior: 'smooth' });
  }, [messages.length, messages[messages.length - 1]?.content]);

  const canSend = (draft.trim().length > 0 || (pendingFiles && pendingFiles.length > 0)) && !isStreaming;

  const handleFiles = useCallback(async (fileList) => {
    const arr = Array.from(fileList || []);
    if (!arr.length) return;
    // Add provisional loading chips immediately
    const placeholders = arr.map((f) => ({
      id: 'f-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7),
      name: f.name,
      size: f.size,
      type: f.type,
      kind: 'loading',
      loading: true,
    }));
    setPendingFiles((prev) => [...(prev || []), ...placeholders]);

    // Read in parallel
    const loaded = await Promise.all(arr.map((f, i) =>
      loadFile(f).then((res) => ({ ...res, id: placeholders[i].id }))
    ));
    setPendingFiles((prev) => {
      const next = [...(prev || [])];
      for (const l of loaded) {
        const idx = next.findIndex((x) => x.id === l.id);
        if (idx >= 0) next[idx] = l;
      }
      return next;
    });
  }, [setPendingFiles]);

  const onPickFile = (e) => {
    const list = e.target.files;
    handleFiles(list);
    e.target.value = ''; // allow re-selecting the same file
  };

  const removeFile = useCallback((id) => {
    setPendingFiles((prev) => (prev || []).filter((f) => f.id !== id));
  }, [setPendingFiles]);

  // Drag-and-drop on the whole chat sidebar
  const onDragEnter = (e) => {
    if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {
      e.preventDefault();
      setDragging(true);
    }
  };
  const onDragOver = (e) => {
    if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'copy';
    }
  };
  const onDragLeave = (e) => {
    // Only set off when leaving the sidebar entirely
    if (e.currentTarget === e.target) setDragging(false);
  };
  const onDrop = (e) => {
    if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
      e.preventDefault();
      handleFiles(e.dataTransfer.files);
    }
    setDragging(false);
  };

  const submit = useCallback(() => {
    if (!canSend) return;
    // Don't allow sending while any file is still loading
    if ((pendingFiles || []).some((f) => f.loading)) return;
    onSend(draft.trim(), pendingQuote || null, pendingFiles || null);
    setDraft('');
    setPendingQuote(null);
    setPendingFiles([]);
  }, [canSend, draft, pendingQuote, pendingFiles, onSend, setPendingQuote, setPendingFiles]);

  const onKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      submit();
    }
  };

  const showEmpty = messages.length === 0;
  const anyLoading = (pendingFiles || []).some((f) => f.loading);

  return (
    <aside
      className={`chat ${dragging ? 'dragging' : ''}`}
      onDragEnter={onDragEnter}
      onDragOver={onDragOver}
      onDragLeave={onDragLeave}
      onDrop={onDrop}
    >
      {dragging ? <div className="drag-hint">Drop file to attach</div> : null}
      <div className="chat-hd">
        <div className="brand">
          <div className="mark">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 3v18M3 12h18" />
              <path d="M12 7l2.5 5L12 17l-2.5-5z" fill="currentColor" stroke="none" />
            </svg>
          </div>
          <span>Strategy Writer</span>
        </div>
        <div className="chat-hd-actions">
          <button className="icon-btn" title="New document" onClick={onNewChat}>
            <Icon.Plus />
          </button>
        </div>
      </div>

      <div className="chat-thread" ref={threadRef}>
        {showEmpty ? (
          <div style={{ paddingTop: 4 }}>
            <div className="msg assistant">
              <div className="who">Claude</div>
              <div className="body">
                What are we writing today? I'll draft it as a markdown document on the right — you can edit it directly, attach reference files, or ask me to change sections.
              </div>
              <div className="chips">
                {STARTER_PROMPTS.map((p, i) => (
                  <button key={i} className="chip" onClick={() => { setDraft(p); taRef.current?.focus(); }}>
                    {p}
                  </button>
                ))}
              </div>
            </div>
          </div>
        ) : null}

        {messages.map((m, i) => (
          <MessageView key={m.id || i} msg={m} onJumpToDoc={onJumpToDoc} />
        ))}
      </div>

      <div className="composer-wrap">
        <div className="composer">
          {pendingFiles && pendingFiles.length ? (
            <div className="files-row">
              {pendingFiles.map((f) => (
                <FileChip key={f.id} file={f} onRemove={removeFile} />
              ))}
            </div>
          ) : null}
          {pendingQuote ? (
            <div className="quote-chip">
              <div className="qtext">{truncate(pendingQuote, 320)}</div>
              <button className="qx" onClick={() => setPendingQuote(null)} title="Remove quote">×</button>
            </div>
          ) : null}
          <textarea
            ref={taRef}
            placeholder={
              pendingQuote
                ? "Rewrite, shorten, or ask about the quoted section…"
                : (pendingFiles && pendingFiles.length
                    ? "Tell Claude what to do with the attached file(s)…"
                    : (docExists ? "Ask for changes, or describe what to write next…" : "Describe the document you want to write…"))
            }
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={onKey}
            rows={1}
          />
          <div className="composer-row">
            <div className="left">
              <input
                ref={fileInputRef}
                type="file"
                multiple
                style={{ display: 'none' }}
                onChange={onPickFile}
                accept=".md,.mdx,.markdown,.txt,.json,.csv,.tsv,.yml,.yaml,.toml,.xml,.html,.css,.js,.jsx,.ts,.tsx,.py,.rb,.go,.rs,.sh,.sql,.docx,.pdf,text/*,application/json,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf,image/*"
              />
              <button
                className="attach-btn"
                title="Attach file"
                onClick={() => fileInputRef.current?.click()}
              >
                <Icon.Paperclip />
              </button>
              {pendingQuote ? (
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, marginLeft: 4 }}>
                  <Icon.Quote /> Quoted
                </span>
              ) : null}
            </div>
            <button
              className="send-btn"
              disabled={!canSend || anyLoading}
              onClick={submit}
              title={anyLoading ? "Reading files…" : "Send (Enter)"}
            >
              <Icon.Send />
            </button>
          </div>
        </div>
        <div className="composer-hint">
          {anyLoading
            ? "Reading attached files…"
            : "Enter to send · Shift+Enter for newline · Drop files to attach"}
        </div>
      </div>
    </aside>
  );
}

window.Chat = Chat;
