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: '故事版。用「做生意其實不划算」當反差開頭，帶出倉儲成本、老婆愛用濕巾、韓國濕巾啟發，以及為什麼淨淨還是決定把濕巾規格拉高。',
    guest: '',
    props: '淨淨簡單柔濕巾、洗衣精箱或示意棧板圖、倉儲空間示意、產品近拍',
    location: '辦公室、倉庫或居家桌面',
    interview: '這支重點不是賣產品，是講真話建立信任。搭配的人可以一直吐槽「那你幹嘛做？」讓笙闆被問到一時語塞，再接回產品初衷。控制 45-60 秒。',
    script: '┬ 起\n【場地】 倉庫／辦公室\n\n【動作】 笙闆站在一堆濕巾箱旁邊，手上拿一包淨淨濕巾\n├ 笙闆：「你知道嗎？如果只用做生意的角度來看，濕紙巾其實一點都不划算。」\n\n┬ 承\n【框架】 倉儲成本與棧板價值對比。\n\n┬ 轉\n【框架】 為什麼家裡真的需要，才決定把規格拉高。\n\n┬ 合\n【框架】 導到下一支不吹不黑實測。',
    extra: '畫面建議：倉庫或紙箱 B-roll、洗衣精箱 vs 濕巾箱對比、老婆日常抽濕巾示意、產品放在家裡或車內的質感空景。',
    note: '備註：成本數字用「大概」「可能」口吻。不要講得像財報公告。故事點到為止，最後導到第 10 支實測。',
    clientNote: '修改空間：互動者可以更嘴一點，例如問「所以你是為了太太虧錢？」笙闆可以回「這句不要給我老婆看到。」',
  },
  {
    id: '第29期-10-不吹不黑淨淨濕紙巾篇',
    rowIndex: 'local-10',
    episode: '第29期',
    filmed: false,
    number: '10',
    series: '淨淨簡單柔濕巾',
    heat: '',
    type: '實測比較',
    audience: '成分講究、體驗講究，願意為寶寶和家人花錢的媽媽',
    title: '不吹不黑、淨淨濕紙巾篇',
    summary: '實測版。用「擦手擦桌子沒差，但擦嘴擦臉擦小孩肌膚就不一樣」切入，帶出嬰兒濕巾與一般濕巾差異，並用厚度、透光度、保水性、拉扯做視覺實測。',
    guest: '',
    props: '淨淨簡單柔濕巾、他牌濕紙巾 2-3 款、手機手電筒、透明板、水、拉扯測試道具',
    location: '辦公室、居家桌面',
    interview: '搭檔負責問笙闆難回答或開玩笑的問題，例如「你自己的產品當然說好啊？」、「這包是濕巾還是小棉被？」讓笙闆用不吹不黑的方式接回實測。',
    script: '┬ 起\n【場地】 辦公室桌面\n\n【動作】 桌上擺三包濕紙巾：一般濕紙巾、嬰兒濕紙巾、淨淨濕紙巾。搭檔先拿起最薄的一包。\n├ 搭檔：「濕紙巾不都差不多嗎？擦一擦而已，為什麼有的可以差到好幾倍價格？」\n├ 笙闆：「所以今天不講感覺，直接測。」\n\n┬ 承\n【框架】 嬰兒濕紙巾 vs 一般濕紙巾規格差異。\n\n┬ 轉\n【框架】 厚度、透光度、保水性、拉扯實測。\n\n┬ 合\n【框架】 回到媽媽 TA：擦嘴、擦臉、擦小孩肌膚，不要只看單價。',
    extra: '畫面建議：全成分標示近拍、手機手電筒透光測試、同時拉扯、擦透明板保水測試、按壓蓋單手開、放在客廳/車內/餐桌的質感畫面。',
    note: '備註：PIF 請講「產品資訊檔案」，不要講 PIF 認證。全台最厚若要講，建議以 70gsm 搭配實測畫面呈現。',
    clientNote: '修改空間：可以把搭檔問題再設計得更鬧，例如「你是不是只是把濕紙巾做成棉被？」、「擦桌子用這個會不會太奢侈？」',
  },
  {
    id: '第29期-11-7-11雲泡啤酒',
    rowIndex: 'local-11',
    episode: '第29期',
    filmed: false,
    number: '11',
    series: '原料解析',
    heat: '',
    type: '框架待討論',
    audience: '喜歡拆解網路新品、便利商店話題商品的觀眾',
    title: '7-11 雲泡啤酒',
    summary: '先放題目與框架。方向可從便利商店新品、泡沫口感、成分與行銷包裝切入，腳本細節待討論。',
    guest: '',
    props: '7-11 雲泡啤酒、透明杯、一般啤酒對照、手機近拍',
    location: '辦公室、便利商店外、桌面實測區',
    interview: '待討論：這題要先判斷是拆口感、拆泡沫原理，還是拆便利商店新品行銷。',
    script: '┬ 起\n【場地】 辦公室／便利商店外\n\n【動作】 拿出 7-11 雲泡啤酒，先展示產品外觀。\n├ 笙闆：「這支先留題目，腳本細節待討論。」\n\n┬ 承\n【框架】 拆解雲泡啤酒跟一般啤酒的差異。\n\n┬ 轉\n【框架】 實測泡沫、口感、視覺呈現。\n\n┬ 合\n【框架】 給結論與留言互動。',
    extra: '可補：泡沫近拍、倒入透明杯、跟一般啤酒泡沫比較。',
    note: '備註：先保留框架，不寫死結論。',
    clientNote: '修改空間：等確認產品賣點後再補完整台詞。',
  },
  {
    id: '第29期-12-自動蓋子',
    rowIndex: 'local-12',
    episode: '第29期',
    filmed: false,
    number: '12',
    series: '原料解析',
    heat: '',
    type: '框架待討論',
    audience: '喜歡看生活小物實測、踩雷與是否值得買的觀眾',
    title: '自動蓋子',
    summary: '先放題目與框架。方向可從生活小物到底實不實用、使用場景、故障率、清潔難度切入。',
    guest: '',
    props: '自動蓋子、對照普通蓋子、杯子或容器、實測道具',
    location: '辦公室、居家桌面',
    interview: '待討論：這題可以拍成「看起來很聰明，但真的方便嗎？」的實測。',
    script: '┬ 起\n【場地】 辦公室桌面\n\n【動作】 展示自動蓋子開合，先製造一點荒謬感。\n├ 笙闆：「這支先留題目，腳本細節待討論。」\n\n┬ 承\n【框架】 它解決什麼問題？是不是真的比普通蓋子方便？\n\n┬ 轉\n【框架】 實測開合速度、穩定度、清潔難度、使用情境。\n\n┬ 合\n【框架】 給值得買或不值得買的結論。',
    extra: '可補：慢動作開合、手忙腳亂測試、普通蓋子對照。',
    note: '備註：先保留框架，等產品到手後補實測細節。',
    clientNote: '修改空間：可做成偏搞笑，也可做成認真生活小物評測。',
  },
  {
    id: '第29期-13-凱恩博士漱口水',
    rowIndex: 'local-13',
    episode: '第29期',
    filmed: false,
    number: '13',
    series: '專業知識',
    heat: '',
    type: '框架待討論',
    audience: '在意口腔清潔、成分與日常護理的觀眾',
    title: '凱恩博士漱口水',
    summary: '先放題目與框架。方向可從漱口水成分、刺激感、酒精與否、日常口腔清潔迷思切入。',
    guest: '',
    props: '凱恩博士漱口水、透明杯、他牌漱口水、成分標示近拍',
    location: '辦公室、洗手台旁、桌面實測區',
    interview: '待討論：這題要注意合規，不做醫療療效宣稱，主打成分解析與使用體驗。',
    script: '┬ 起\n【場地】 辦公室／洗手台旁\n\n【動作】 拿出凱恩博士漱口水，拍包裝與成分標示。\n├ 笙闆：「這支先留題目，腳本細節待討論。」\n\n┬ 承\n【框架】 漱口水到底在清潔什麼？一般人常見誤解是什麼？\n\n┬ 轉\n【框架】 看成分、刺激感、味道、使用情境，跟他牌做比較。\n\n┬ 合\n【框架】 給使用建議與留言互動。',
    extra: '可補：成分近拍、倒入透明杯、他牌對照、使用後感受口播。',
    note: '備註：避免殺菌、治療口臭、牙周病等醫療宣稱。',
    clientNote: '修改空間：等確認產品定位後再決定是知識型還是實測型。',
  },
];
const STAGE_PATTERN = /^┬\s*(起|承|內容|轉|合|起承轉合)?/;

function clean(value) {
  return String(value ?? '').replace(/\r/g, '').replaceAll('笙哥', '笙闆').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));
  const keepSheetNumbers = new Set(['5', '6']);
  return [...sheetScripts.filter((item) => keepSheetNumbers.has(item.number) && !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 />);
