import React, { useEffect, useMemo, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import Papa from 'papaparse';
import {
  BookOpen,
  Check,
  ChevronLeft,
  ChevronRight,
  ClipboardList,
  LoaderCircle,
  MapPin,
  Package,
  PanelLeftClose,
  PanelLeftOpen,
  RefreshCw,
  Search,
  Send,
  Sparkles,
  StickyNote,
} from 'lucide-react';

const CSV_URL =
  'https://docs.google.com/spreadsheets/d/17bEn7jrp7QO4YJ5DMgCNU05NbgI9CCV97qdBhDGHd6I/gviz/tq?tqx=out:csv&gid=2127261155';
const AI_ENDPOINT =
  typeof window !== 'undefined' && ['127.0.0.1', 'localhost'].includes(window.location.hostname)
    ? 'https://video.rsway.net/api/assistant'
    : '/api/assistant';
const NOTES_ENDPOINT =
  typeof window !== 'undefined' && ['127.0.0.1', 'localhost'].includes(window.location.hostname)
    ? 'https://video.rsway.net/api/notes'
    : '/api/notes';
const SCRIPT_EDITS_ENDPOINT =
  typeof window !== 'undefined' && ['127.0.0.1', 'localhost'].includes(window.location.hostname)
    ? 'https://video.rsway.net/api/scripts'
    : '/api/scripts';

const FIELD_MAP = {
  episode: 0,
  filmed: 1,
  number: 2,
  series: 3,
  heat: 4,
  type: 5,
  audience: 6,
  title: 7,
  summary: 8,
  guest: 9,
  props: 10,
  location: 11,
  interview: 12,
  script: 13,
  extra: 14,
  note: 15,
  clientNote: 16,
};

const EXTRA_SCRIPTS = [
  {
    id: '第29期-9-為什麼我做了一個其實不太划算的濕紙巾',
    rowIndex: 'local-9',
    episode: '第29期',
    filmed: false,
    number: '9',
    series: '淨淨簡單柔濕巾',
    heat: '',
    type: '故事版',
    audience: '成分講究、體驗講究，願意為寶寶和家人花錢的媽媽',
    title: '為什麼我做了一個其實不太划算的濕紙巾？',
    summary:
      '用商業成本切入，講濕紙巾倉儲成本高、毛利壓力大，但因為家裡真的大量使用、又想做出 MIT 高規格好貨，所以才硬做。',
    guest: '',
    props: '淨淨簡單柔濕巾、洗衣精箱或示意棧板圖、倉儲空間示意、產品近拍',
    location: '辦公室、倉庫或居家桌面',
    interview: '這支故事點到為止，控制在 45-60 秒。核心不是賣產品，是建立「這不是因為好賺才做」的真實感。',
    script: [
      '┬ 起',
      '你知道嗎？',
      '如果只用做生意的角度來看，濕紙巾其實一點都不划算。',
      '',
      '一個棧板的洗衣精，團購或經銷商價大概可以到 40 萬。',
      '但一個棧板的濕紙巾，可能只有 4 萬。',
      '',
      '同樣佔一個倉儲位置，濕紙巾的倉儲成本壓力，可能是 10 倍以上。',
      '',
      '┬ 承',
      '更麻煩的是，我們這款又做得很厚。',
      '紙的磅數拉高，保水度也拉高，量還沒衝起來之前，成本真的不漂亮。',
      '',
      '我本來其實沒有很想做。',
      '',
      '但我老婆太愛用濕紙巾了，用量很大。',
      '我們在台灣買過很多款，總覺得差一點。',
      '',
      '┬ 轉',
      '後來有一次用到一款韓國濕紙巾，很厚、很保水，擦起來手感真的很好。',
      '那時候我才覺得，原來濕紙巾可以做到這個等級。',
      '',
      '但我們也遇過朋友肌膚比較敏感，用某些濕紙巾擦臉後整張臉紅起來。',
      '那時候就覺得很可惜。',
      '厚度跟手感做得好，配方跟成分也應該一起講究。',
      '',
      '┬ 合',
      '所以這款不是因為好賺才做。',
      '是因為我們自己家真的需要，才硬把規格拉上去。',
      '',
      '我想證明，不是只有韓國做得到。',
      '台灣 MIT 也可以做出高規格濕紙巾。',
      '',
      '下一支，我直接拿它跟一般濕紙巾測。',
      '厚度、保水、透光度、拉扯，全部攤開來看。',
    ].join('\n'),
    extra:
      '拍攝節奏：前 3 秒先講「不划算」製造反差；中段成本數字要清楚；故事不要講太長，最後導到下一支實測。',
    note:
      '備註：可補倉庫 B-roll、棧板示意圖。數字 40 萬 / 4 萬若要正式上片，拍攝前再確認是否以「大概」口吻呈現。',
    clientNote:
      '修改空間：如果想更溫情，可以多放老婆使用情境；如果想更商業，可以多放倉儲成本和毛利壓力。',
  },
  {
    id: '第29期-10-不吹不黑淨淨濕紙巾篇',
    rowIndex: 'local-10',
    episode: '第29期',
    filmed: false,
    number: '10',
    series: '淨淨簡單柔濕巾',
    heat: '',
    type: '實測比較',
    audience: '成分講究、體驗講究，願意為寶寶和家人花錢的媽媽',
    title: '不吹不黑、淨淨濕紙巾篇',
    summary:
      '延續頻道「平心而論，不吹不黑」風格，用嬰兒濕紙巾 vs 一般濕紙巾差異切入，再實測厚度、透光度、保水性、堅固度。',
    guest: '',
    props: '淨淨簡單柔濕巾、他牌濕紙巾 2-3 款、手機手電筒、透明板、水、拉扯測試道具',
    location: '辦公室、居家桌面',
    interview:
      '這支要控制在 45-60 秒。不要像硬業配，口吻要像平常分析別人產品：平心而論，不吹不黑。',
    script: [
      '┬ 起',
      '平心而論，不吹不黑。',
      '今天講自己的產品，我標準會更嚴格。',
      '',
      '如果只是擦手、擦桌子，老實說差別可能沒那麼大。',
      '但如果是擦嘴、擦臉，甚至是擦小孩肌膚，那真的就不一樣了。',
      '',
      '今天不講感覺。',
      '我們直接看成分透明度、厚度、保水性、透光度、堅固度。',
      '',
      '┬ 承',
      '很多人不知道，一般濕紙巾跟嬰兒濕紙巾，規格本來就不同。',
      '',
      '一般濕紙巾很多是一般用品，依法不一定需要揭露全成分。',
      '包裝上可能只寫主成分，你不一定看得到完整配方。',
      '',
      '但嬰兒濕紙巾是用化粧品規格管理。',
      '要做化粧品登錄，也要準備 PIF 產品資訊檔案。',
      '',
      '所以它貴，不只是因為包裝漂亮。',
      '後面的成分、用料、規格，本來就不是同一個等級。',
      '',
      '┬ 轉',
      '我們這款直接把全成分標出來，而且是中英文完整標示。',
      '你翻過來就看得到，不用猜。',
      '',
      '水的部分，我們用的是 EDI 純水。',
      '裡面也有金縷梅、蘆薈這類保養品等級的成分。',
      '',
      '不是說濕紙巾會變保養品，濕紙巾就是濕紙巾。',
      '但你要擦臉、擦嘴、擦小孩肌膚，這些細節我覺得就有差。',
      '',
      '┬ 合',
      '接下來直接測。',
      '',
      '第一，厚度。淨淨這款是 70gsm，你一摸就知道不是薄薄一張。',
      '第二，透光度。對著光看，差異很直觀。',
      '第三，保水性。太快乾，擦到後面其實是在磨。',
      '第四，拉扯。擦一擦會破、會起屑、會沾手，體驗就差很多。',
      '',
      '我不是說每個人都一定要買高規格濕紙巾。',
      '但如果你是拿來擦嘴、擦臉、擦小孩肌膚，真的不要只看一包多少錢。',
      '',
      '你們想看我拿哪幾款來盲測？留言，我下一支直接測給你看。',
    ].join('\n'),
    extra:
      '拍攝節奏：第一段先建立「不吹不黑」公信力；第二段講規格差異；最後用四段快測收束。故事不要展開，留給第 9 支。',
    note:
      '備註：合規用語注意「PIF 產品資訊檔案」不是認證；全台最厚若要講，建議搭配 70gsm 與可比對樣品，不要單獨喊絕對宣稱。',
    clientNote:
      '修改空間：可把實測順序調成「透光度先上」增加視覺衝擊；也可加一句按壓蓋，但不要讓它搶走主線。',
  },
];

const STAGE_PATTERN = /^┬\s*(起|承|內容|轉|合|起承轉合)?/;

function clean(value) {
  return String(value ?? '').replace(/\r/g, '').trim();
}

function stageTitle(raw, index) {
  const text = clean(raw).replace(/^┬\s*/, '').trim();
  if (text) return text === '內容' ? '承' : text;
  return ['起', '承', '轉', '合'][index] || `段落 ${index + 1}`;
}

function splitSegments(script) {
  const lines = clean(script).split('\n');
  const segments = [];
  let current = null;

  lines.forEach((line) => {
    if (STAGE_PATTERN.test(line.trim())) {
      if (current) segments.push(current);
      current = { label: stageTitle(line, segments.length), lines: [] };
      return;
    }
    if (!current) current = { label: '腳本', lines: [] };
    current.lines.push(line);
  });

  if (current) segments.push(current);
  return segments
    .map((segment, index) => ({
      id: `${segment.label}-${index}`,
      label: segment.label,
      text: segment.lines.join('\n').trim(),
    }))
    .filter((segment) => segment.text || segment.label);
}

function parseRows(rows) {
  const sheetScripts = rows
    .slice(1)
    .map((row, index) => {
      const get = (key) => clean(row[FIELD_MAP[key]]);
      return {
        id: `${get('episode') || '未分期'}-${get('number') || index + 1}-${get('title')}`,
        rowIndex: index + 2,
        episode: get('episode'),
        filmed: get('filmed') === 'TRUE',
        number: get('number'),
        series: get('series'),
        heat: get('heat'),
        type: get('type'),
        audience: get('audience').replace(/^TA[:：]\s*/, ''),
        title: get('title'),
        summary: get('summary'),
        guest: get('guest'),
        props: get('props'),
        location: get('location'),
        interview: get('interview'),
        script: get('script'),
        extra: get('extra'),
        note: get('note'),
        clientNote: get('clientNote'),
      };
    })
    .filter((item) => item.title && item.script);

  const extraIds = new Set(EXTRA_SCRIPTS.map((item) => item.id));
  return [...sheetScripts.filter((item) => !extraIds.has(item.id)), ...EXTRA_SCRIPTS];
}

function useSharedNotes() {
  const saveTimers = useRef(new Map());
  const [notes, setNotes] = useState(() => {
    try {
      return JSON.parse(localStorage.getItem('script-reader-notes') || '{}');
    } catch {
      return {};
    }
  });
  const [syncStatus, setSyncStatus] = useState('讀取雲端備註');

  useEffect(() => {
    let active = true;

    async function loadNotes() {
      try {
        const response = await fetch(NOTES_ENDPOINT, { cache: 'no-store' });
        const data = await response.json();
        if (!response.ok) throw new Error(data.error || '讀取備註失敗');
        if (!active) return;
        const cloudNotes = data.notes || {};
        setNotes((current) => {
          const next = { ...current, ...cloudNotes };
          localStorage.setItem('script-reader-notes', JSON.stringify(next));
          return next;
        });
        setSyncStatus('雲端備註已同步');
      } catch {
        if (active) setSyncStatus('備註暫存本機，稍後再同步');
      }
    }

    loadNotes();
    return () => {
      active = false;
      saveTimers.current.forEach((timer) => clearTimeout(timer));
      saveTimers.current.clear();
    };
  }, []);

  const updateNote = (key, value) => {
    setNotes((current) => {
      const next = { ...current, [key]: value };
      localStorage.setItem('script-reader-notes', JSON.stringify(next));
      return next;
    });
    setSyncStatus('儲存中');

    const existingTimer = saveTimers.current.get(key);
    if (existingTimer) clearTimeout(existingTimer);

    const timer = setTimeout(async () => {
      try {
        const response = await fetch(NOTES_ENDPOINT, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ key, value }),
        });
        const data = await response.json().catch(() => ({}));
        if (!response.ok) throw new Error(data.error || '儲存備註失敗');
        setSyncStatus('雲端備註已同步');
      } catch {
        setSyncStatus('備註暫存本機，稍後再同步');
      } finally {
        saveTimers.current.delete(key);
      }
    }, 450);

    saveTimers.current.set(key, timer);
  };

  return [notes, updateNote, syncStatus];
}

function useScriptEdits() {
  const [edits, setEdits] = useState({});
  const [editSyncStatus, setEditSyncStatus] = useState('腳本雲端同步');

  useEffect(() => {
    let active = true;

    async function loadEdits() {
      try {
        const response = await fetch(SCRIPT_EDITS_ENDPOINT, { cache: 'no-store' });
        const data = await response.json();
        if (!response.ok) throw new Error(data.error || '讀取腳本修改失敗');
        if (!active) return;
        setEdits(data.edits || {});
        setEditSyncStatus('腳本已同步');
      } catch {
        if (active) setEditSyncStatus('腳本同步失敗');
      }
    }

    loadEdits();
    return () => {
      active = false;
    };
  }, []);

  const saveEdit = async (id, patch) => {
    const cleanPatch = Object.fromEntries(
      Object.entries(patch).map(([key, value]) => [key, typeof value === 'string' ? value : value ?? ''])
    );
    setEditSyncStatus('儲存腳本中');
    setEdits((current) => ({ ...current, [id]: { ...(current[id] || {}), ...cleanPatch } }));

    const response = await fetch(SCRIPT_EDITS_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ id, patch: cleanPatch }),
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok) {
      setEditSyncStatus('腳本儲存失敗');
      throw new Error(data.error || '腳本儲存失敗');
    }
    setEdits(data.edits || {});
    setEditSyncStatus('腳本已儲存');
  };

  return [edits, saveEdit, editSyncStatus];
}

function InfoPill({ icon: Icon, label, value }) {
  if (!value) return null;
  return (
    <div className="info-pill">
      <Icon size={15} />
      <span>{label}</span>
      <strong>{value}</strong>
    </div>
  );
}

function TextBlock({ title, children }) {
  if (!children) return null;
  return (
    <section className="text-block">
      <h3>{title}</h3>
      <p>{children}</p>
    </section>
  );
}

function AiPanel({ selected, segments }) {
  const chatEndRef = useRef(null);
  const initialMessages = [
    {
      role: 'assistant',
      content: '我會跟著這支腳本陪你討論。可以先問開頭鉤子、補拍鏡位、轉場台詞，也可以接著上一句繼續改。',
    },
  ];
  const [messages, setMessages] = useState(initialMessages);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState('');

  const examples = ['幫我設計 5 種不同的開頭鉤子', '這支可以怎麼補拍 B-roll？', '幫我想 3 種更自然的收尾 CTA'];

  useEffect(() => {
    chatEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
  }, [messages, isLoading]);

  useEffect(() => {
    setMessages(initialMessages);
    setInput('');
    setError('');
  }, [selected?.id]);

  const askAi = async (prompt = input) => {
    const question = prompt.trim();
    if (!question || isLoading || !selected) return;

    const nextMessages = [...messages, { role: 'user', content: question }];
    setMessages(nextMessages);
    setInput('');
    setError('');
    setIsLoading(true);

    try {
      const response = await fetch(AI_ENDPOINT, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          question,
          script: selected,
          segments,
          messages: nextMessages.slice(-14),
        }),
      });
      const data = await response.json();
      if (!response.ok) throw new Error(data.error || 'AI 暫時無法回覆');
      setMessages((current) => [...current, { role: 'assistant', content: data.answer }]);
    } catch (requestError) {
      setError(requestError.message || 'AI 暫時無法回覆');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <section className="ai-panel">
      <div className="ai-panel-head">
        <div>
          <Sparkles size={18} />
          <h3>現場 AI 助理</h3>
        </div>
        <span>讀取目前腳本</span>
      </div>
      <button className="chat-reset" type="button" onClick={() => setMessages(initialMessages)}>
        新對話
      </button>

      <div className="quick-prompts">
        {examples.map((example) => (
          <button key={example} onClick={() => askAi(example)} disabled={isLoading}>
            {example}
          </button>
        ))}
      </div>

      <div className="chat-window">
        {messages.map((message, index) => (
          <div className={`chat-bubble ${message.role}`} key={`${message.role}-${index}`}>
            {message.content}
          </div>
        ))}
        {isLoading && (
          <div className="chat-bubble assistant loading">
            <LoaderCircle size={15} />
            思考中
          </div>
        )}
        <div ref={chatEndRef} />
      </div>

      {error && <div className="chat-error">{error}</div>}

      <form
        className="chat-input"
        onSubmit={(event) => {
          event.preventDefault();
          askAi();
        }}
      >
        <textarea
          value={input}
          onChange={(event) => setInput(event.target.value)}
          placeholder="問：這段開頭怎麼拍更吸引人？"
          rows={3}
        />
        <button type="submit" disabled={isLoading || !input.trim()} aria-label="送出問題">
          {isLoading ? <LoaderCircle size={18} /> : <Send size={18} />}
        </button>
      </form>
    </section>
  );
}

function App() {
  const [scripts, setScripts] = useState([]);
  const [selectedId, setSelectedId] = useState('');
  const [query, setQuery] = useState('');
  const [episode, setEpisode] = useState('全部');
  const [status, setStatus] = useState('讀取中');
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [notes, updateNote, noteSyncStatus] = useSharedNotes();
  const [scriptEdits, saveScriptEdit, editSyncStatus] = useScriptEdits();
  const [editMode, setEditMode] = useState(false);
  const [draft, setDraft] = useState(null);
  const [editError, setEditError] = useState('');

  const loadData = async () => {
    setStatus('讀取中');
    try {
      const response = await fetch(CSV_URL, { cache: 'no-store' });
      const csv = await response.text();
      const parsed = Papa.parse(csv, { skipEmptyLines: true });
      const items = parseRows(parsed.data);
      setScripts(items);
      setSelectedId((current) => current || items[0]?.id || '');
      setStatus(`已同步 ${items.length} 支腳本`);
    } catch (error) {
      setStatus('讀取失敗，請重新整理');
    }
  };

  useEffect(() => {
    loadData();
  }, []);

  const editableScripts = useMemo(
    () => scripts.map((item) => ({ ...item, ...(scriptEdits[item.id] || {}) })),
    [scripts, scriptEdits]
  );

  const episodes = useMemo(
    () => ['全部', ...Array.from(new Set(editableScripts.map((item) => item.episode).filter(Boolean)))],
    [editableScripts]
  );

  const filtered = useMemo(() => {
    const keyword = query.trim().toLowerCase();
    return editableScripts.filter((item) => {
      const matchEpisode = episode === '全部' || item.episode === episode;
      const haystack = [
        item.title,
        item.series,
        item.type,
        item.location,
        item.props,
        item.audience,
        item.summary,
        item.script,
      ]
        .join(' ')
        .toLowerCase();
      return matchEpisode && (!keyword || haystack.includes(keyword));
    });
  }, [editableScripts, query, episode]);

  const selected = editableScripts.find((item) => item.id === selectedId) || filtered[0] || editableScripts[0];
  const segments = selected ? splitSegments(selected.script) : [];
  const selectedIndex = Math.max(
    0,
    filtered.findIndex((item) => item.id === selected?.id)
  );
  const canGoPrev = filtered.length > 1;
  const canGoNext = filtered.length > 1;

  const selectByOffset = (offset) => {
    if (!filtered.length) return;
    const nextIndex = (selectedIndex + offset + filtered.length) % filtered.length;
    setSelectedId(filtered[nextIndex].id);
  };

  useEffect(() => {
    if (filtered.length && !filtered.some((item) => item.id === selectedId)) {
      setSelectedId(filtered[0].id);
    }
  }, [filtered, selectedId]);

  useEffect(() => {
    const onKeyDown = (event) => {
      if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
        return;
      }
      if (event.key === 'ArrowLeft') selectByOffset(-1);
      if (event.key === 'ArrowRight') selectByOffset(1);
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [filtered, selectedIndex]);

  useEffect(() => {
    if (!selected) return;
    setDraft({
      title: selected.title || '',
      summary: selected.summary || '',
      location: selected.location || '',
      props: selected.props || '',
      script: selected.script || '',
      interview: selected.interview || '',
      extra: selected.extra || '',
      note: selected.note || '',
      clientNote: selected.clientNote || '',
    });
    setEditError('');
  }, [selected?.id, editMode]);

  const updateDraft = (key, value) => {
    setDraft((current) => ({ ...(current || {}), [key]: value }));
  };

  const saveDraft = async () => {
    if (!selected || !draft) return;
    setEditError('');
    try {
      await saveScriptEdit(selected.id, draft);
      setEditMode(false);
    } catch (error) {
      setEditError(error.message || '腳本儲存失敗');
    }
  };

  return (
    <main className="app-shell">
      <aside className={`sidebar ${sidebarOpen ? 'is-open' : 'is-closed'}`}>
        <div className="brand">
          <div className="brand-mark">
            <BookOpen size={22} />
          </div>
          <div>
            <h1>笙闆腳本閱讀器</h1>
            <p>{status}</p>
          </div>
        </div>

        <div className="search-box">
          <Search size={17} />
          <input
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="搜尋主題、地點、道具"
          />
        </div>

        {episodes.length > 2 && (
          <div className="episode-row">
            {episodes.map((name) => (
              <button
                key={name}
                className={episode === name ? 'active' : ''}
                onClick={() => setEpisode(name)}
              >
                {name}
              </button>
            ))}
          </div>
        )}

        <div className="script-list">
          {filtered.map((item) => (
            <button
              key={item.id}
              className={`script-item ${selected?.id === item.id ? 'selected' : ''}`}
              onClick={() => setSelectedId(item.id)}
            >
              <span className="script-meta">
                {item.number ? `第 ${item.number} 支` : '未編號'}
              </span>
              <strong>{item.title}</strong>
              <span>{item.location || item.series}</span>
            </button>
          ))}
        </div>
      </aside>

      <section className="reader">
        <header className="topbar">
          <button
            className="icon-button"
            onClick={() => setSidebarOpen((value) => !value)}
            aria-label="切換腳本清單"
          >
            {sidebarOpen ? <PanelLeftClose size={20} /> : <PanelLeftOpen size={20} />}
          </button>
          <div className="current-script">
            <span>明日拍攝工作台</span>
            <strong>{selected?.episode || '尚未選擇'} · 第 {selected?.number || '-'} 支</strong>
          </div>
          <div className="stepper">
            <button onClick={() => selectByOffset(-1)} disabled={!canGoPrev} aria-label="上一支腳本">
              <ChevronLeft size={18} />
            </button>
            <span>
              {filtered.length ? selectedIndex + 1 : 0} / {filtered.length}
            </span>
            <button onClick={() => selectByOffset(1)} disabled={!canGoNext} aria-label="下一支腳本">
              <ChevronRight size={18} />
            </button>
          </div>
          <button className="sync-button" onClick={loadData}>
            <RefreshCw size={16} />
            重新讀取表格
          </button>
          <button className={`sync-button ${editMode ? 'is-active' : ''}`} onClick={() => setEditMode((value) => !value)}>
            <StickyNote size={16} />
            {editMode ? '離開編輯' : '編輯模式'}
          </button>
        </header>

        {selected && (
          <div className="reader-grid">
            <article className="script-detail">
              <div className="title-area">
                <div className="status-row">
                  <span className={selected.filmed ? 'done-chip' : 'todo-chip'}>
                    {selected.filmed ? '已拍攝' : '待拍攝'}
                  </span>
                  <span>{selected.series}</span>
                  <span>{selected.type}</span>
                </div>
                {editMode ? (
                    <div className="edit-panel title-editor">
                    <label>
                      標題
                      <input value={draft?.title || ''} onChange={(event) => updateDraft('title', event.target.value)} />
                    </label>
                    <label>
                      說明
                      <textarea
                        value={draft?.summary || ''}
                        onChange={(event) => updateDraft('summary', event.target.value)}
                        rows={3}
                      />
                    </label>
                    <div className="edit-grid">
                      <label>
                        地點
                        <input
                          value={draft?.location || ''}
                          onChange={(event) => updateDraft('location', event.target.value)}
                        />
                      </label>
                      <label>
                        道具
                        <input value={draft?.props || ''} onChange={(event) => updateDraft('props', event.target.value)} />
                      </label>
                    </div>
                    <div className="edit-actions sticky-edit-actions">
                      <span>{editSyncStatus}</span>
                      {editError && <strong>{editError}</strong>}
                      <button type="button" onClick={saveDraft}>
                        儲存腳本
                      </button>
                    </div>
                  </div>
                ) : (
                  <>
                    <h2>{selected.title}</h2>
                    {selected.summary && <p>{selected.summary}</p>}
                  </>
                )}
              </div>

              <div className="info-grid">
                <InfoPill icon={MapPin} label="地點" value={selected.location} />
                <InfoPill icon={Package} label="道具" value={selected.props} />
              </div>

              {editMode ? (
                <div className="edit-panel script-editor">
                  <label>
                    完整腳本
                    <textarea
                      className="script-textarea"
                      value={draft?.script || ''}
                      onChange={(event) => updateDraft('script', event.target.value)}
                      rows={18}
                      placeholder="用「┬ 起」「┬ 承」「┬ 轉」「┬ 合」分段，網站會自動拆成橋段。"
                    />
                  </label>
                </div>
              ) : (
                <div className="segments">
                  {segments.map((segment, index) => {
                  const noteKey = `${selected.id}::${segment.id}`;
                  return (
                    <section className="segment" key={segment.id}>
                      <div className="segment-head">
                        <span>{String(index + 1).padStart(2, '0')}</span>
                        <h3>{segment.label}</h3>
                      </div>
                      <pre>{segment.text}</pre>
                      <label className="note-editor">
                        <span>
                          <StickyNote size={16} />
                          橋段備註
                        </span>
                        <textarea
                          value={notes[noteKey] || ''}
                          onChange={(event) => updateNote(noteKey, event.target.value)}
                          placeholder="現場要補拍、語氣、鏡位、道具提醒都可以寫這裡"
                        />
                      </label>
                    </section>
                  );
                  })}
                </div>
              )}
            </article>

            <aside className="side-panel">
              <AiPanel selected={selected} segments={segments} />
              <div className="panel-card">
                <h3>
                  <ClipboardList size={18} />
                  訪綱
                </h3>
                {editMode ? (
                  <textarea
                    className="side-edit"
                    value={draft?.interview || ''}
                    onChange={(event) => updateDraft('interview', event.target.value)}
                    rows={6}
                  />
                ) : (
                  <p>{selected.interview || '這支尚未填訪綱。'}</p>
                )}
              </div>
              <div className="panel-card">
                <h3>
                  <BookOpen size={18} />
                  補充資訊
                </h3>
                {editMode ? (
                  <textarea
                    className="side-edit"
                    value={draft?.extra || ''}
                    onChange={(event) => updateDraft('extra', event.target.value)}
                    rows={6}
                  />
                ) : (
                  <p>{selected.extra || '這支尚未填補充資訊。'}</p>
                )}
              </div>
              {editMode ? (
                <>
                  <div className="panel-card">
                    <h3>原表備註</h3>
                    <textarea
                      className="side-edit"
                      value={draft?.note || ''}
                      onChange={(event) => updateDraft('note', event.target.value)}
                      rows={5}
                    />
                  </div>
                  <div className="panel-card">
                    <h3>客戶筆記／回饋</h3>
                    <textarea
                      className="side-edit"
                      value={draft?.clientNote || ''}
                      onChange={(event) => updateDraft('clientNote', event.target.value)}
                      rows={5}
                    />
                  </div>
                </>
              ) : (
                <>
                  <TextBlock title="原表備註">{selected.note}</TextBlock>
                  <TextBlock title="客戶筆記／回饋">{selected.clientNote}</TextBlock>
                </>
              )}
              <div className="save-hint">
                <Check size={17} />
                {noteSyncStatus}
              </div>
            </aside>
          </div>
        )}
      </section>
    </main>
  );
}

createRoot(document.getElementById('root')).render(<App />);
