// auxio.js — Clean, block-based transport

import { ROUTES, get, setVerbatimStatus } from './auxsystem.js';

export const lineOut = { items: [], history: [] };
export const lineIn = { items: [] };

// ─────────────────────────────────────────────────────────────
// HTTP
// ─────────────────────────────────────────────────────────────

export function endpoint(path = '') {
  const host = get('verbatim.host') || '127.0.0.1';
  const port = get('verbatim.port') || '';
  return `http://${host}${port ? `:${port}` : ''}${path}`;
}

export async function getHealth() {
  try {
    const res = await fetch(endpoint('/health'), { method: 'GET' });
    setVerbatimStatus(res.ok);

    return {
      ok: Boolean(res.ok),
      status: res.status,
      endpoint: endpoint('/health')
    };
  } catch (error) {
    setVerbatimStatus(false);

    return {
      ok: false,
      status: 'ERR',
      endpoint: endpoint('/health'),
      msg: error?.message || 'Connection failure'
    };
  }
}

export async function post(path, payload) {
  try {
    const res = await fetch(endpoint(path), {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload ?? {})
    });

    const text = await res.text();
    let data = null;

    if (text) {
      try { data = JSON.parse(text); }
      catch { data = text; }
    }

    setVerbatimStatus(res.ok);

    return {
      ok: Boolean(res.ok),
      status: res.status,
      endpoint: endpoint(path),
      msg: typeof data === 'string' ? data : data?.message || data?.msg || data?.error || '',
      data
    };
  } catch (error) {
    setVerbatimStatus(false);

    return {
      ok: false,
      status: 'ERR',
      endpoint: endpoint(path),
      msg: error?.message || 'Connection failure'
    };
  }
}

// ─────────────────────────────────────────────────────────────
// INTAKE
// ─────────────────────────────────────────────────────────────

export async function handleDetectedItems(items = []) {
  const list = Array.isArray(items) ? items : [items];
  const results = [];

  for (const item of list) {
    results.push(await handleDetectedItem(item));
  }

  return {
    ok: results.some(result => result?.ok),
    count: results.length,
    results
  };
}

export async function handleDetectedItem(item = {}) {
  const command = String(item.command || item.action || item.Action || '').trim().toLowerCase();
  const payload = item.payload ?? item.Payload ?? item.text ?? item.rawText ?? '';

  if (!command || !hasValue(payload)) {
    return { ok: false, msg: 'Missing command or payload' };
  }

  if (command === 'codetoaux') return codetoaux(payload, item);
  if (command === 'jsontoaux') return jsontoaux(payload, item);
  if (command === 'jsontovrbm') return jsontovrbm(payload, item);
  if (command === 'inqtoaux') return inqtoaux(payload, item);
  if (command === 'inqtovrbm') return inqtovrbm(payload, item);
  if (command === 'inqautoreply') return inqautoreply(payload, item);

  return { ok: false, msg: `Unknown command: ${command}` };
}

// ─────────────────────────────────────────────────────────────
// LINE OUT
// ─────────────────────────────────────────────────────────────

export function addCodeToLineOut(payload, source = {}) {
  const raw = String(payload || '');

  const row = {
    id: makeId('lo'),
    type: 'code',
    line: 'out',
    command: 'codetoaux',
    title: 'CODE NEEDS DETAILS',
    block: raw,
    raw,
    Action: '',
    FilePath: '',
    Payload: { input: raw },
    needsDetails: true,
    status: 'details-needed',
    source
  };

  lineOut.items.push(row);
  emitIOChange('lineout');

  return row;
}

export async function codetoaux(payload, source = {}) {
  const row = addCodeToLineOut(payload, source);
  return { ok: true, msg: 'Code queued', item: row };
}

export function addJsonToLineOut(payload, source = {}) {
  const packet = parseInput(payload);

  const row = {
    id: makeId('lo'),
    type: 'json',
    line: 'out',
    command: 'jsontoaux',
    title: titleOf(packet, 'RecordingSheet'),
    block: blockText(packet),
    packet,
    needsDetails: false,
    status: 'ready',
    source
  };

  lineOut.items.push(row);
  emitIOChange('lineout');

  return row;
}

export async function jsontoaux(payload, source = {}) {
  const row = addJsonToLineOut(payload, source);
  return { ok: true, msg: 'JSON queued', item: row };
}
export async function jsontovrbm(payload, source = {}) {
  return sendPacket(ROUTES.verbatimWrite, parseInput(payload));
}

export function compileCodeToPacket(indexOrRow, action, filePath) {
  const row = typeof indexOrRow === 'number'
    ? lineOut.items[indexOrRow]
    : indexOrRow;

  if (!row || row.type !== 'code') return null;

  const cleanAction = String(action || '').trim();
  const cleanFilePath = String(filePath || '').trim();
  const raw = String(row.raw || row.block || '').trim();
  const title = cleanAction && cleanFilePath ? `${cleanAction}: ${cleanFilePath}` : 'CODE NEEDS DETAILS';

  row.Action = cleanAction;
  row.FilePath = cleanFilePath;
  row.Payload = { input: raw };
  row.title = title;
  row.status = cleanAction && cleanFilePath && raw ? 'ready' : 'details-needed';
  row.needsDetails = row.status !== 'ready';

  if (!row.needsDetails) {
    row.type = 'json';
    row.command = 'codetovrbm';
    row.packet = {
      Task: title,
      Action: cleanAction,
      FilePath: cleanFilePath,
      Payload: { input: raw }
    };
    row.block = blockText(row.packet);
  }

  emitIOChange('lineout');

  return row;
}
export function completeCodeDetails(row, action, filePath) {
  return compileCodeToPacket(row, action, filePath);
}

export function codesheetupdate(row, action, filePath) {
  return compileCodeToPacket(row, action, filePath);
}

export async function codesendvrbm(indexOrRow) {
  return sendLineOutItem(indexOrRow);
}

export async function jsonsendvrbm(indexOrRow) {
  return sendLineOutItem(indexOrRow);
}

export async function sendLineOutItem(indexOrRow) {
  const row = typeof indexOrRow === 'number'
    ? lineOut.items[indexOrRow]
    : indexOrRow;

  if (!row) return { ok: false, msg: 'LINE OUT item not found' };

  if (row.type === 'code') {
    return { ok: false, msg: 'Code must be completed before SEND', item: row };
  }

  const result = await sendPacket(ROUTES.verbatimWrite, row.packet ?? row.payload ?? row);

  if (result.ok) {
    removeLineOutRow(row, 'sent');
  } else {
    row.status = 'failed';
    row.result = result;
    emitIOChange('lineout');
  }

  return { item: row, result };
}

export async function sendAllLineOut() {
  const ready = lineOut.items.filter(item => item.type !== 'code');
  const results = [];

  for (const item of [...ready]) {
    results.push(await sendLineOutItem(item));
  }

  return {
    ok: results.every(result => result.result?.ok),
    count: results.length,
    results
  };
}

export function discardLineOutItem(index) {
  const row = lineOut.items[index];
  if (!row) return null;

  lineOut.items.splice(index, 1);
  addLineOutHistory(row, 'cancelled');
  emitIOChange('lineout');

  return row;
}

export function clearLineOut() {
  lineOut.items.length = 0;
  emitIOChange('lineout');
}

function removeLineOutRow(row, status = 'sent') {
  const index = lineOut.items.indexOf(row);
  if (index >= 0) lineOut.items.splice(index, 1);

  addLineOutHistory(row, status);
  emitIOChange('lineout');
}

function addLineOutHistory(row, status) {
  const copy = { ...row, status, historyAt: Date.now() };
  lineOut.history.unshift(copy);
  lineOut.history.length = Math.min(lineOut.history.length, 200);
  return copy;
}

// ─────────────────────────────────────────────────────────────
// LINE IN
// ─────────────────────────────────────────────────────────────

export async function inqtoaux(payload, source = {}) {
  const row = {
    id: makeId('li'),
    type: 'inq',
    line: 'in',
    command: 'inqtoaux',
    title: titleOf(payload, 'Inquiry'),
    packet: parseInput(payload),
    status: 'ready',
    source
  };

  lineIn.items.push(row);
  emitIOChange('linein');

  return { ok: true, msg: 'Inquiry queued', item: row };
}

export async function inqtovrbm(payload, source = {}) {
  return handleInquiryDirect(parseInput(payload), false, source);
}

export async function inqautoreply(payload, source = {}) {
  return handleInquiryDirect(parseInput(payload), true, source);
}

export function addFileInquiryToLineIn(payload, source = {}) {
  return inqtoaux(payload, source);
}

export async function requestFileInquiry(payload, source = {}) {
  return inqtovrbm(payload, source);
}

export async function autoFileInquiry(payload, source = {}) {
  return inqautoreply(payload, source);
}

export async function inqsendvrbm(indexOrId, source = {}) {
  const row = findLineInRow(indexOrId);
  return handleInquiryDirect(row?.packet ?? row?.payload ?? indexOrId, false, source);
}

async function handleInquiryDirect(packet, autoFeed = false, source = {}) {
  const result = await sendPacket(ROUTES.verbatimRead, packet);

  if (!result.ok) return result;

  const reply = result.data;

  if (isFiletreePacket(packet, reply)) {
    return { ok: true, data: reply, msg: 'Filetree returned for menu' };
  }

  if (autoFeed) {
    return injectTextToChat(reply);
  }

  const row = {
    id: makeId('li'),
    type: 'inq',
    line: 'in',
    command: 'inqtovrbm',
    title: titleOf(reply, titleOf(packet, 'Inquiry Reply')),
    packet,
    reply,
    status: 'ready',
    source
  };

  lineIn.items.push(row);
  emitIOChange('linein');

  return { ok: true, data: reply, item: row };
}

export async function retrieve(packet = {}) {
  return handleInquiryDirect(packet, false, packet?.source || 'inquiry-menu').then(result => {
    if (!result.ok) throw new Error(result.msg || 'Inquiry failed');
    return result.data;
  });
}

export async function retrieveRaw(packet = {}) {
  const result = await sendPacket(ROUTES.verbatimRead, packet);
  if (!result.ok) throw new Error(result.msg || 'Inquiry failed');
  return result.data;
}

export async function feedLineInItem(indexOrId) {
  const row = findLineInRow(indexOrId);

  if (!row) return { ok: false, msg: 'LINE IN item not found' };

  const result = await injectTextToChat(row.reply ?? row.packet ?? row.payload);

  if (result.ok) {
    removeLineInRow(row);
  }

  return result;
}

export async function inqfeedaux(indexOrId) {
  return feedLineInItem(indexOrId);
}

export async function feedAllLineIn() {
  const ready = lineIn.items.filter(item => item.status !== 'waiting' && item.status !== 'failed');
  const text = ready.map(item => blockText(item.reply ?? item.packet ?? item.payload)).filter(Boolean).join('\n\n');

  if (!text.trim()) return { ok: true, count: 0 };

  const result = await injectTextToChat(text);

  if (result.ok) {
    ready.forEach(removeLineInRow);
  }

  return { ...result, count: result.ok ? ready.length : 0 };
}

export function clearLineIn() {
  lineIn.items.length = 0;
  emitIOChange('linein');
}

function removeLineInRow(row) {
  const index = lineIn.items.indexOf(row);
  if (index >= 0) lineIn.items.splice(index, 1);
  emitIOChange('linein');
}

function findLineInRow(indexOrId) {
  return typeof indexOrId === 'number'
    ? lineIn.items[indexOrId]
    : lineIn.items.find(item => item.id === indexOrId);
}

// ─────────────────────────────────────────────────────────────
// HARDWARE
// ─────────────────────────────────────────────────────────────

export async function runHardwareButton(action) {
  const clean = String(action || '').trim().toLowerCase();

  if (clean === 'stop') {
    clearLineIn();
    clearLineOut();
    return { ok: true, action: 'stop' };
  }

  if (clean === 'play') {
    const feed = await feedAllLineIn();
    const send = await sendAllLineOut();
    return { ok: true, action: 'play', feed, send };
  }

  if (clean === 'next') {
    const send = await sendAllLineOut();
    return { ok: true, action: 'next', send };
  }

  return { ok: false, action: clean, msg: 'Button action not connected' };
}

// ─────────────────────────────────────────────────────────────
// CORE UTILITIES
// ─────────────────────────────────────────────────────────────

async function sendPacket(route, payload) {
  try {
    const result = await post(route, payload);
    return {
      ok: Boolean(result.ok),
      status: result.status,
      msg: result.msg,
      data: result.data ?? result
    };
  } catch (error) {
    return { ok: false, msg: error?.message || 'Request failed' };
  }
}

export async function injectTextToChat(value) {
  const text = blockText(value);
  if (!text.trim()) return { ok: false, msg: 'Nothing to paste' };

  try {
    try {
      await navigator.clipboard?.writeText?.(text);
    } catch {}

    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    if (typeof tab?.id !== 'number') return { ok: false, msg: 'No active tab' };

    const response = await chrome.tabs.sendMessage(tab.id, {
      type: 'AUX_CHAT_INSERT',
      text
    });

    return response?.ok
      ? { ok: true }
      : { ok: false, msg: response?.msg || 'Feed failed' };
  } catch (error) {
    return { ok: false, msg: error?.message || 'Feed failed' };
  }
}

export function blockText(value) {
  if (typeof value === 'string') return value;
  if (value === null || typeof value === 'undefined') return '';
  return JSON.stringify(value, null, 2);
}

export function toFeedText(value) {
  return blockText(value);
}

function parseInput(value) {
  if (value && typeof value === 'object') return value;

  try {
    return JSON.parse(String(value || '').trim());
  } catch {
    return value;
  }
}

function titleOf(value, fallback = 'AUX ITEM') {
  if (typeof value === 'string') {
    return value.trim().split(/\r?\n/).find(Boolean)?.slice(0, 90) || fallback;
  }

  if (!value || typeof value !== 'object') return fallback;

  return String(
    value.title ||
    value.Task ||
    value.Enhancement ||
    value.Diagnostic ||
    value.FilePath ||
    value.filePath ||
    value.target ||
    value.path ||
    value.request ||
    value.command ||
    fallback
  ).trim();
}

function isFiletreePacket(packet = {}, reply = {}) {
  const request = String(packet?.request || reply?.request || '').trim().toLowerCase();

  return request === 'getfiletree' ||
    request === 'read-tree' ||
    request === 'filetree' ||
    request === 'prj-map' ||
    request === 'update.prj-map';
}

function hasValue(value) {
  if (value === null || typeof value === 'undefined') return false;
  if (typeof value === 'string') return Boolean(value.trim());
  return true;
}

function makeId(prefix = 'aux') {
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
}

function emitIOChange(source = 'auxio') {
  window.dispatchEvent(new CustomEvent('aux:io-changed', {
    detail: { source }
  }));
}