/* ============================================================
   Playground UI — React app
   ============================================================ */

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

// ---- Per-OS picker: accept a string/fn or { unix, windows } object ----
function pickOS(value, os) {
  if (value == null) return value;
  if (typeof value === 'object' && !Array.isArray(value) && (value.unix || value.windows || value.mac || value.linux)) {
    if (os === 'windows') return value.windows ?? value.unix ?? value.mac;
    if (os === 'linux')   return value.linux ?? value.unix ?? value.mac;
    return value.mac ?? value.unix ?? value.linux;
  }
  return value;
}

function resolveStep(step, os) {
  if (!step) return step;
  return {
    ...step,
    say: pickOS(step.say, os),
    hint: pickOS(step.hint, os),
    why: pickOS(step.why, os),
    try: pickOS(step.try, os),
    matches: pickOS(step.matches, os),
  };
}

function resolveLesson(lesson, os) {
  if (!lesson) return lesson;
  return {
    ...lesson,
    summary: pickOS(lesson.summary, os),
    steps: lesson.steps.map(s => resolveStep(s, os)),
  };
}

// ---- Tiny helpers ----
function clone(o) { return JSON.parse(JSON.stringify(o)); }
function escapeHtml(s) {
  return String(s || '')
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function renderOut(text) {
  // Allow specific spans (<span class="dir">, <span class="file">, <span class="dim">)
  // but escape everything else.
  if (!text) return '';
  // Split out our allowed spans
  const ALLOWED = /(<span class="(?:dir|file|dim)">[^<]*<\/span>)/g;
  const parts = text.split(ALLOWED);
  return parts.map(p => {
    if (ALLOWED.test(p)) { ALLOWED.lastIndex = 0; return p; }
    return escapeHtml(p);
  }).join('');
}

// ============================================================
// Terminal component
// ============================================================
function Terminal({ os, cwd, lines, onSubmit, inputRef, awaiting }) {
  const [draft, setDraft] = useState('');
  const [histPos, setHistPos] = useState(-1);
  const bodyRef = useRef(null);

  // History from previous commands in lines
  const cmdHistory = useMemo(() =>
    lines.filter(l => l.kind === 'cmd').map(l => l.text), [lines]
  );

  useEffect(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
  }, [lines, awaiting]);

  function onKeyDown(e) {
    if (e.key === 'Enter') {
      e.preventDefault();
      onSubmit(draft);
      setDraft('');
      setHistPos(-1);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (cmdHistory.length === 0) return;
      const next = histPos < 0 ? cmdHistory.length - 1 : Math.max(0, histPos - 1);
      setHistPos(next);
      setDraft(cmdHistory[next]);
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (histPos < 0) return;
      const next = histPos + 1;
      if (next >= cmdHistory.length) {
        setHistPos(-1);
        setDraft('');
      } else {
        setHistPos(next);
        setDraft(cmdHistory[next]);
      }
    } else if (e.key === 'l' && (e.ctrlKey || e.metaKey)) {
      e.preventDefault();
      onSubmit('clear');
    }
  }

  const promptCwd = VFS.promptCwd(cwd, VFS.HOME, os);
  const promptUser = os === 'windows' ? 'PS' : 'eva@laptop';
  const promptEnd  = os === 'windows' ? '>' : '$';
  const promptClass = os === 'windows' ? 'prompt-win' : 'prompt';

  return (
    <div className="term" onClick={() => inputRef.current && inputRef.current.focus()}>
      <div className="term-chrome">
        <div className="dots"><span></span><span></span><span></span></div>
        <div className="title">{os === 'windows' ? 'PowerShell 7' : os === 'mac' ? 'Terminal — zsh' : 'bash'}</div>
        <div style={{width: 60}}></div>
      </div>
      <div className="term-body" ref={bodyRef}>
        {lines.map((l, i) => {
          if (l.kind === 'cmd') {
            return (
              <div key={i} className="term-row">
                <span className={promptClass}>{l.prompt}</span> <span className="cmd">{l.text}</span>
              </div>
            );
          }
          if (l.kind === 'err') {
            return <div key={i} className="err" dangerouslySetInnerHTML={{__html: renderOut(l.text)}}></div>;
          }
          if (l.kind === 'sys') {
            return <div key={i} className="dim" dangerouslySetInnerHTML={{__html: l.text}}></div>;
          }
          return <div key={i} className="out" dangerouslySetInnerHTML={{__html: renderOut(l.text)}}></div>;
        })}
        <div className="term-row">
          <span className={promptClass}>
            {os === 'windows' ? `PS ${promptCwd}>` : `${promptUser} ${promptCwd} ${promptEnd}`}
          </span>{' '}
          <input
            ref={inputRef}
            className="term-input"
            value={draft}
            spellCheck={false}
            autoFocus
            autoComplete="off"
            autoCorrect="off"
            autoCapitalize="off"
            onChange={e => setDraft(e.target.value)}
            onKeyDown={onKeyDown}
            aria-label="terminal input"
          />
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Step card (instruction above the terminal in guided mode)
// ============================================================
function StepCard({ lesson, stepIdx, onHint, hintShown, onReveal, revealed, onSkip, onNext, completed }) {
  if (!lesson) return null;
  const step = lesson.steps[stepIdx];
  if (!step) return null;
  const total = lesson.steps.length;

  return (
    <div className="step-card">
      <div className="step-head">
        <div className="step-meta">
          <span className="step-num">Step {stepIdx + 1} of {total}</span>
          <span className="step-dots">
            {lesson.steps.map((_, i) => (
              <span key={i} className={'dot ' + (i < stepIdx || (i === stepIdx && completed) ? 'done' : i === stepIdx ? 'now' : '')}></span>
            ))}
          </span>
        </div>
        <button className="link-btn" onClick={onSkip}>Skip step →</button>
      </div>
      <div className="step-body" dangerouslySetInnerHTML={{__html: step.say}}></div>

      {hintShown && step.hint && (
        <div className="step-hint">
          <span className="kind-label">Hint</span>
          <div dangerouslySetInnerHTML={{__html: step.hint}}></div>
        </div>
      )}

      {revealed && step.why && (
        <div className="step-why">
          <span className="kind-label">The why</span>
          <div dangerouslySetInnerHTML={{__html: step.why}}></div>
        </div>
      )}

      <div className="step-actions">
        {!completed && !hintShown && step.hint && (
          <button className="btn ghost" onClick={onHint}>Show hint</button>
        )}
        {completed && !revealed && step.why && (
          <button className="btn ghost" onClick={onReveal}>Reveal the why</button>
        )}
        {completed && (
          <>
            <span className="completed-pill">✓ Done</span>
            <button className="btn next-btn" onClick={onNext}>
              {stepIdx + 1 < total ? 'Next step →' : 'Finish lesson →'}
            </button>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// Lesson sidebar
// ============================================================
function LessonList({ active, onPick, completed, onFreePlay, mode, os }) {
  return (
    <aside className="lesson-rail">
      <div className="rail-section">
        <h4>Curriculum</h4>
        {Lessons.map(l => {
          const done = (completed[l.id] || 0) >= l.steps.length;
          const inProg = (completed[l.id] || 0) > 0 && !done;
          const summary = pickOS(l.summary, os);
          return (
            <button
              key={l.id}
              className={'lesson-link ' + (active === l.id && mode === 'guided' ? 'active' : '')}
              onClick={() => onPick(l.id)}
            >
              <div className="ll-title">
                {l.title}
                {done && <span className="badge done">✓</span>}
                {inProg && <span className="badge prog">●</span>}
              </div>
              <div className="ll-sum">{summary}</div>
              <div className="ll-time">{l.time} · {l.kind}</div>
            </button>
          );
        })}
      </div>
      <div className="rail-section">
        <h4>Sandbox</h4>
        <button
          className={'lesson-link ' + (mode === 'free' ? 'active' : '')}
          onClick={onFreePlay}
        >
          <div className="ll-title">Free play</div>
          <div className="ll-sum">All 25 commands. Your own simulated filesystem. Nothing to do — just explore.</div>
          <div className="ll-time">no script</div>
        </button>
      </div>
    </aside>
  );
}

// ============================================================
// State panel — "Where you are"
// ============================================================
function StatePanel({ cwd, fs, os, lastLayer }) {
  const node = VFS.walk(fs, cwd);
  const entries = node && node.type === 'dir' ? Object.entries(node.children) : [];
  entries.sort(([a], [b]) => a.localeCompare(b));

  // Path crumbs
  const parts = cwd === '/' ? [] : cwd.split('/').filter(Boolean);
  const home = VFS.HOME.split('/').filter(Boolean);
  let isHome = parts.length >= home.length && home.every((p, i) => parts[i] === p);
  const crumbs = isHome
    ? ['~', ...parts.slice(home.length)]
    : ['/', ...parts];

  return (
    <aside className="state-panel">
      <div className="state-section">
        <h4>You are here</h4>
        <div className="crumbs">
          {crumbs.map((c, i) => (
            <React.Fragment key={i}>
              <span className={'crumb' + (i === crumbs.length - 1 ? ' here' : '')}>{c}</span>
              {i < crumbs.length - 1 && <span className="sep">{c === '~' || c === '/' ? '' : '/'}{c === '~' || c === '/' ? '/' : ''}</span>}
            </React.Fragment>
          ))}
        </div>
        <div className="full-path">{VFS.displayPath(cwd, os)}</div>
      </div>

      <div className="state-section">
        <h4>In this folder ({entries.length})</h4>
        {entries.length === 0 && <div className="empty">(empty)</div>}
        <ul className="ls">
          {entries.map(([name, n]) => (
            <li key={name} className={n.type === 'dir' ? 'dir' : 'file'}>
              <span className="icon">{n.type === 'dir' ? '▸' : '·'}</span>
              <span className="name">{name}{n.type === 'dir' ? '/' : ''}</span>
            </li>
          ))}
        </ul>
      </div>

      {lastLayer && (
        <div className="state-section">
          <h4>Who did what (last command)</h4>
          <div className="layer-trace">
            {lastLayer.map((step, i) => (
              <div key={i} className={'trace ' + step.color}>
                <span className="trace-actor">{step.actor}</span>
                <span className="trace-action">{step.action}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </aside>
  );
}

// ============================================================
// Main app
// ============================================================
function PlaygroundApp() {
  const [os, setOs] = useState(window.getOS ? window.getOS() : 'mac');
  const [mode, setMode] = useState('guided'); // 'guided' | 'free'
  const [activeLessonId, setActiveLessonId] = useState(Lessons[0].id);
  const [stepIdx, setStepIdx] = useState(0);
  const [hintShown, setHintShown] = useState(false);
  const [revealed, setRevealed] = useState(false);
  const [completed, setCompleted] = useState({}); // {lessonId: stepIndex completed up to}
  const [stepDone, setStepDone] = useState(false);

  // OS-switch confirmation modal state
  const [osConfirm, setOsConfirm] = useState(null);
  // { newOs, currentOs, resolve } | null

  // Terminal state
  const [fs, setFs] = useState(VFS.makeFS());
  const [cwd, setCwd] = useState(VFS.HOME);
  const [lines, setLines] = useState(() => welcomeLines('guided'));
  const [history, setHistory] = useState([]);
  const [lastLayer, setLastLayer] = useState(null);
  const inputRef = useRef(null);

  // Sync OS toggle
  useEffect(() => {
    const handler = e => setOs(e.detail.os);
    window.addEventListener('os-change', handler);
    return () => window.removeEventListener('os-change', handler);
  }, []);

  // Lock OS toggle once the curriculum has been started.
  // Register a guard that os-toggle.js consults before applying a change.
  // Use a ref-like read of latest state inside the guard via closure-fresh
  // re-registration on every render. That's fine — guard is just a property.
  const hasProgress =
    Object.values(completed).some(n => n > 0) || stepIdx > 0 || stepDone;
  useEffect(() => {
    window.__osChangeGuard = (newOs, currentOs) => {
      if (!hasProgress) return true;
      // Return a promise resolved by the modal's buttons.
      return new Promise(resolve => {
        setOsConfirm({ newOs, currentOs, resolve });
      });
    };
    return () => {
      if (window.__osChangeGuard) delete window.__osChangeGuard;
    };
  }, [hasProgress]);

  function confirmOsSwitch() {
    if (!osConfirm) return;
    // Wipe curriculum progress and reset the lesson UI.
    setCompleted({});
    setActiveLessonId(Lessons[0].id);
    setStepIdx(0);
    setHintShown(false);
    setRevealed(false);
    setStepDone(false);
    setMode('guided');
    // Also reset the sandbox so the new OS starts from a clean state.
    setFs(VFS.makeFS());
    setCwd(VFS.HOME);
    setLines(welcomeLines('guided'));
    setHistory([]);
    setLastLayer(null);
    osConfirm.resolve(true);
    setOsConfirm(null);
  }

  function cancelOsSwitch() {
    if (!osConfirm) return;
    osConfirm.resolve(false);
    setOsConfirm(null);
  }

  function welcomeLines(modeKind) {
    if (modeKind === 'guided') {
      return [
        { kind: 'sys', text: 'Terminal Tutor · simulated shell. No real files are touched.' },
        { kind: 'sys', text: 'A lesson is loaded. Read the instructions above, then type below.' },
      ];
    }
    return [
      { kind: 'sys', text: 'Free play. Type <code>help</code> for a list of commands.' },
      { kind: 'sys', text: 'This is a simulated filesystem — experiment freely. Reset anytime.' },
    ];
  }

  const lesson = useMemo(() =>
    resolveLesson(Lessons.find(l => l.id === activeLessonId), os), [activeLessonId, os]
  );
  const currentStep = lesson && mode === 'guided' ? lesson.steps[stepIdx] : null;

  function pickLesson(id) {
    setActiveLessonId(id);
    setMode('guided');
    const upTo = completed[id] || 0;
    setStepIdx(Math.min(upTo, Lessons.find(l => l.id === id).steps.length - 1));
    setHintShown(false);
    setRevealed(false);
    setStepDone(false);
    setTimeout(() => inputRef.current && inputRef.current.focus(), 50);
  }

  function startFreePlay() {
    setMode('free');
    setLines(welcomeLines('free'));
    setTimeout(() => inputRef.current && inputRef.current.focus(), 50);
  }

  function resetAll() {
    setFs(VFS.makeFS());
    setCwd(VFS.HOME);
    setLines(welcomeLines(mode));
    setHistory([]);
    setLastLayer(null);
  }

  function buildLastLayer(cmd) {
    const c = Commands.tokenize(cmd)[0];
    if (!c) return null;
    const canon = Commands.canonicalize(c, os);
    const trace = [
      { color: 'you', actor: 'You',    action: 'typed "' + cmd + '"' },
      { color: 'shell', actor: 'Shell',  action: canon ? 'parsed it as the ' + canon + ' command' : 'failed to find "' + c + '"' },
    ];
    if (!canon) return trace;
    if (['pwd','ls','cat','cd','mkdir','touch','rm','rmdir','cp','mv'].includes(canon)) {
      trace.push({ color: 'kernel', actor: 'Kernel', action: 'looked at the filesystem to ' + (
        canon === 'ls' ? 'list directory entries' :
        canon === 'pwd' ? 'return the working directory' :
        canon === 'cd' ? 'verify the path exists' :
        canon === 'cat' ? 'read the file' :
        canon === 'mkdir' ? 'create a new directory' :
        canon === 'touch' ? 'create or update the file' :
        canon === 'rm' || canon === 'rmdir' ? 'remove the entry' :
        canon === 'cp' ? 'duplicate the file' :
        canon === 'mv' ? 'move the entry' :
        'do its work'
      ) });
    } else if (['echo','clear','help','history','whoami','date'].includes(canon)) {
      trace.push({ color: 'shell', actor: 'Shell', action: 'handled it on its own — no kernel needed' });
    }
    return trace;
  }

  function submit(rawLine) {
    const line = rawLine.replace(/\s+$/, '');
    const state = { os, cwd, fs: clone(fs), history: history.slice() };
    state.history.push(line);

    const prompt = os === 'windows'
      ? `PS ${VFS.promptCwd(cwd, VFS.HOME, os)}>`
      : `eva@laptop ${VFS.promptCwd(cwd, VFS.HOME, os)} ${'$'}`;

    if (!line.trim()) {
      setLines(ls => [...ls, { kind: 'cmd', text: '', prompt }]);
      return;
    }

    const result = Commands.exec(state, line);

    setHistory(state.history);
    setLastLayer(buildLastLayer(line));

    if (result.clear) {
      setLines([{ kind: 'sys', text: 'Screen cleared.' }]);
      setFs(state.fs);
      setCwd(state.cwd);
      checkLessonAdvance(line);
      return;
    }

    const newLines = [{ kind: 'cmd', text: line, prompt }];
    if (result.err) newLines.push({ kind: 'err', text: result.err });
    else if (result.out) newLines.push({ kind: 'out', text: result.out });

    setLines(ls => [...ls, ...newLines]);
    setFs(state.fs);
    setCwd(state.cwd);
    checkLessonAdvance(line);
  }

  function checkLessonAdvance(line) {
    if (mode !== 'guided' || !lesson) return;
    const step = lesson.steps[stepIdx];
    if (!step || stepDone) return;
    let matched = false;
    if (step.matches) matched = step.matches(line);
    else if (Array.isArray(step.try)) matched = step.try.includes(line.trim());
    else matched = line.trim() === step.try;
    if (matched) {
      setStepDone(true);
      setCompleted(c => ({ ...c, [lesson.id]: Math.max(c[lesson.id] || 0, stepIdx + 1) }));
    }
  }

  function advanceStep() {
    if (!lesson) return;
    if (stepIdx + 1 < lesson.steps.length) {
      setStepIdx(stepIdx + 1);
      setHintShown(false);
      setRevealed(false);
      setStepDone(false);
    } else {
      // Lesson finished — mark, push a sys line
      setLines(ls => [...ls, { kind: 'sys', text: '✓ Lesson complete: ' + lesson.title }]);
      setStepDone(false);
    }
  }

  // When user reveals why, advance after a short pause? No — let them click "Next".
  function reveal() {
    setRevealed(true);
  }

  // After "why" is shown, manual next button
  function nextStep() {
    advanceStep();
  }

  return (
    <div className="pg-shell">
      {/* Mode tabs + reset */}
      <div className="pg-mode">
        <div className="tabs">
          <button
            className={'tab ' + (mode === 'guided' ? 'active' : '')}
            onClick={() => { setMode('guided'); }}
          >
            Guided lessons
          </button>
          <button
            className={'tab ' + (mode === 'free' ? 'active' : '')}
            onClick={startFreePlay}
          >
            Free play
          </button>
        </div>
        <div className="pg-mode-right">
          <span className="hint">Type <span className="kbd">help</span> in the terminal for a command list</span>
          <button className="btn ghost small" onClick={resetAll}>Reset sandbox</button>
        </div>
      </div>

      <div className="pg-grid">
        <LessonList
          active={activeLessonId}
          onPick={pickLesson}
          onFreePlay={startFreePlay}
          completed={completed}
          mode={mode}
          os={os}
        />

        <div className="pg-main">
          {mode === 'guided' && lesson && (
            <StepCard
              lesson={lesson}
              stepIdx={stepIdx}
              completed={stepDone}
              hintShown={hintShown}
              revealed={revealed}
              onHint={() => setHintShown(true)}
              onReveal={reveal}
              onSkip={() => advanceStep()}
              onNext={() => advanceStep()}
            />
          )}

          {mode === 'free' && (
            <div className="free-banner">
              <div>
                <div className="eyebrow">Free play</div>
                <h3>Your own simulated machine.</h3>
                <p>Same filesystem you saw in the lessons. Try anything — make folders, move files, try a command you don't know. Use <code className="inline">help</code> for the menu. <code className="inline">↑</code> recalls previous commands.</p>
              </div>
              <button className="btn ghost small" onClick={resetAll}>Reset filesystem</button>
            </div>
          )}

          <Terminal
            os={os}
            cwd={cwd}
            lines={lines}
            onSubmit={submit}
            inputRef={inputRef}
            awaiting={!!currentStep}
          />
        </div>

        <StatePanel cwd={cwd} fs={fs} os={os} lastLayer={lastLayer} />
      </div>

      {osConfirm && (
        <OsSwitchModal
          newOs={osConfirm.newOs}
          currentOs={osConfirm.currentOs}
          onConfirm={confirmOsSwitch}
          onCancel={cancelOsSwitch}
        />
      )}
    </div>
  );
}

// ============================================================
// OS-switch confirmation modal
// ============================================================
function OsSwitchModal({ newOs, currentOs, onConfirm, onCancel }) {
  const label = o => o === 'mac' ? 'macOS' : o === 'windows' ? 'Windows' : 'Linux';
  useEffect(() => {
    const onKey = e => {
      if (e.key === 'Escape') onCancel();
      else if (e.key === 'Enter') onConfirm();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onConfirm, onCancel]);
  return (
    <div className="os-modal-scrim" onClick={onCancel}>
      <div className="os-modal" role="dialog" aria-modal="true" aria-labelledby="os-modal-title" onClick={e => e.stopPropagation()}>
        <div className="os-modal-eyebrow">Switching shell</div>
        <h3 id="os-modal-title">
          Switch to {label(newOs)}? You'll start the curriculum over.
        </h3>
        <p>
          Lesson scripts, accepted commands, and your "who did what" trace
          are tailored to <strong>{label(currentOs)}</strong>. Changing the
          operating system mid-curriculum would teach you a different shell
          than the one you're holding in your head right now.
        </p>
        <p className="os-modal-fineprint">
          Continuing will reset all lesson progress and the sandbox, then
          restart you at <em>Lesson 1 · Where am I?</em> on {label(newOs)}.
        </p>
        <div className="os-modal-actions">
          <button className="btn ghost" onClick={onCancel} autoFocus>Stay on {label(currentOs)}</button>
          <button className="btn" onClick={onConfirm}>Reset &amp; switch to {label(newOs)}</button>
        </div>
      </div>
    </div>
  );
}

window.PlaygroundApp = PlaygroundApp;
