'use strict';
// OpenCode TUI state detector.
//
// OpenCode redraws prompt/footer/status regions and can re-emit startup
// diagnostics while the agent is actually sitting at its input prompt. Those
// frames should still paint in the terminal, but they must not be treated as
// model activity. Approval widgets and real tool output remain active.

const { baseDetector } = require('./base');

const OPENCODE_FOOTER_LINE_RE = /^(?:gpt|claude|codex|gemini|opencode|deepseek|kimi|moonshot|qwen|llama|mistral)[\w.+-]*(?:\s+[\w.+-]+){0,8}\s+[·-]\s+(?:~|\/|\.\.?\/)/i;
const OPENCODE_PROMPT_LINE_RE = /^(?:>|›|❯)\s*(?:\S.*)?$/;
const OPENCODE_BUSY_LINE_RE = /^(?:[•●◦▪·*]\s*)?(?:(?:Waiting\s+for\s+background\s+terminal\b.*)|(?:Working|Thinking|Reading|Writing|Editing|Running|Searching|Planning|Analyzing|Calling|Using|Processing|Crunching)\b.*\b(?:esc|interrupt|cancel)\b)/i;
const OPENCODE_PASSIVE_STATUS_LINE_RE = /^(?:[•●◦▪·*]\s*)?(?:Ready|Waiting|Idle|Stopped|Done|Complete|Completed)\b/i;
const OPENCODE_CONTROL_HINT_RE = /^(?:esc|ctrl-c|ctrl\+c|press\s+enter)\b.*(?:interrupt|cancel|confirm|send)/i;
const OPENCODE_SKILL_WARNING_RE = /^(?:⚠\s*)?Skipped loading\s+\d+\s+skill\(s\)\s+due to invalid SKILL\.md files\./i;
const OPENCODE_SKILL_DESCRIPTION_RE = /^\/.*\/SKILL\.md:\s+invalid description:\s+exceeds maximum length of\s+\d+\s+characters$/i;

function stripAnsiPreserveLines(data) {
  return String(data || '')
    // Cursor-addressed redraws otherwise collapse multiple rows together after
    // stripping CSI. Preserve row boundaries before removing control codes.
    .replace(/\x1b\[[0-9]+;[0-9]+[Hf]/g, '\n')
    .replace(/\x1b\[[0-9]+[ABCD]/g, '\n')
    .replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '')
    .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
    .replace(/\r/g, '\n')
    .replace(/[\x00-\x09\x0b\x0c\x0e-\x1f\x7f]/g, '');
}

function printableLines(data) {
  return stripAnsiPreserveLines(data)
    .replace(/\u00a0/g, ' ')
    .split(/\n+/)
    .map(line => line.trim())
    .filter(Boolean);
}

function isOpenCodePassiveLine(line) {
  const text = String(line || '').trim();
  if (!text) return true;
  return OPENCODE_FOOTER_LINE_RE.test(text) ||
    OPENCODE_PROMPT_LINE_RE.test(text) ||
    OPENCODE_PASSIVE_STATUS_LINE_RE.test(text) ||
    OPENCODE_CONTROL_HINT_RE.test(text) ||
    OPENCODE_SKILL_WARNING_RE.test(text) ||
    OPENCODE_SKILL_DESCRIPTION_RE.test(text);
}

function isOpenCodeBusyLine(line) {
  return OPENCODE_BUSY_LINE_RE.test(String(line || '').trim());
}

// The agent is mid-turn when its busy/spinner line ("Working… esc to cancel") is on
// screen. server.js's generating-gate uses this to suppress the idle-notify so a
// long, output-quiet OpenCode turn whose composer matches a global prompt pattern is
// not mislabeled "Needs You". Tail-scoped; same busy signal detectWaitingInput gates.
function detectActivelyGenerating(text) {
  return printableLines(text).slice(-12).some(isOpenCodeBusyLine);
}

function detectWaitingInput(text) {
  const lines = printableLines(text).slice(-10);
  if (!lines.length) return null;
  if (lines.some(isOpenCodeBusyLine)) return null;
  if (!lines.some(line => OPENCODE_PROMPT_LINE_RE.test(line))) return null;
  return { reason: 'input' };
}

module.exports = {
  ...baseDetector,
  id: 'opencode',
  // OpenCode progress can be sparse, but prompt/footer redraws are filtered
  // below. Keep a provider hold only for meaningful activity frames.
  idleDebounceMs: 10000,

  isActiveChunk(data) {
    if (!baseDetector.isActiveChunk(data)) return false;
    const lines = printableLines(data);
    if (lines.length && lines.every(line => isOpenCodePassiveLine(line) && !isOpenCodeBusyLine(line))) {
      return false;
    }
    return true;
  },

  isStatusOnlyChunk(data) {
    const lines = printableLines(data);
    return !!(lines.length && lines.every(line => isOpenCodePassiveLine(line) || isOpenCodeBusyLine(line)));
  },

  isBusyStatusChunk(data) {
    const lines = printableLines(data);
    return lines.some(isOpenCodeBusyLine);
  },

  detectWaitingInput,
  detectActivelyGenerating,
  _test: {
    printableLines,
    isOpenCodePassiveLine,
    isOpenCodeBusyLine,
  },
};
