// macOS menu bar (status bar) controller.
//
// Surfaces a glanceable, always-visible view of OpenChamber's live state:
//  1. an aggregate activity indicator (idle / busy / error+retry) in the icon
//     title, rendered as a monochrome template image plus a text counter so it
//     adapts to light/dark menu bars (colour can't be shown in template mode);
//  2. pending approvals (permission + question requests) that block agents,
//     with inline Allow/Deny actions;
//  3. the list of active sessions with status + branch, click to focus;
//  4. quick actions (new session, show window, quit).
//
// The live state lives in the renderer (Zustand). It is pushed here over the
// existing IPC bridge via the `desktop_tray_update` command; this module owns
// only presentation. Tray clicks call back through `onAction`, which main.mjs
// routes to the renderer (focus-session, respond-permission, …) or handles
// natively (show-main-window, quit).

import { Tray, Menu, nativeImage } from 'electron';

const MAX_SESSIONS = 8;
const MAX_APPROVALS = 10;

const truncate = (value, max) => {
  const text = typeof value === 'string' ? value.trim() : '';
  if (text.length <= max) return text;
  return `${text.slice(0, Math.max(0, max - 1))}…`;
};

// Which status icon key a session maps to. 'blank' (a transparent image)
// reserves the same left gutter for idle rows so every row aligns.
const statusIconKey = (session) => {
  if (session.status === 'busy') return 'busy';
  if (session.status === 'retry') return 'retry';
  if (session.hasError) return 'error';
  if (session.unseen > 0) return 'unseen';
  return 'blank';
};

const sessionLabel = (session) => {
  // The status is a native left icon (the ✓ already signals unread), so the
  // label is just the session title.
  return truncate(session.title || 'Untitled session', 40);
};

const approvalLabel = (approval) => {
  const icon = approval.kind === 'permission' ? '⛔' : '❓';
  const who = truncate(approval.sessionTitle || 'Session', 24);
  const what = truncate(approval.label || (approval.kind === 'permission' ? 'Permission request' : 'Question'), 34);
  return `${icon} ${who} — ${what}`;
};

// Text shown next to the icon — reserved for the two states where a precise
// count is actionable: pending approvals and errors. Busy and unread are
// conveyed by the icon itself (animated / filled faces), so they add no text.
// Glyphs come from the Geometric Shapes block so macOS renders them monochrome
// (not colour emoji) and tints them with the menu bar like the template icon.
const computeTitle = (counts) => {
  if (counts.approvals > 0) return `◆ ${counts.approvals}`; // decision needed
  if (counts.error > 0) return `▲ ${counts.error}`;         // problem
  return '';
};

// Which icon variant to show. Busy work animates a "breathing" fill; unread
// (with nothing active) holds a static filled cube until the state clears;
// otherwise the plain outline.
const computeIconState = (counts) => {
  if (counts.busy > 0) return 'busy';
  if (counts.unseen > 0) return 'unseen';
  return 'idle';
};

const computeTooltip = (counts, sessionCount) => {
  if (sessionCount === 0) return 'OpenChamber — no active sessions';
  const bits = [];
  if (counts.approvals > 0) bits.push(`${counts.approvals} awaiting approval`);
  if (counts.error > 0) bits.push(`${counts.error} with errors`);
  if (counts.busy > 0) bits.push(`${counts.busy} working`);
  if (counts.unseen > 0) bits.push(`${counts.unseen} unread`);
  const suffix = bits.length ? ` · ${bits.join(', ')}` : ' · idle';
  return `OpenChamber — ${sessionCount} session${sessionCount === 1 ? '' : 's'}${suffix}`;
};

// Frame cadence for the "breathing" busy animation. With the eased frame set
// (denser near the extremes) a slower tick reads as a calm, continuous glow
// rather than a snappy blink.
const ANIM_INTERVAL_MS = 75;

const toTemplateImage = (p) => {
  const image = nativeImage.createFromPath(p);
  image.setTemplateImage(true);
  return image;
};

// idleIconPath: plain outline (calm state). unseenIconPath: statically filled
// (a finished session left unread). breathIconPaths: eased outline→fill frames
// the busy state ping-pongs through.
export const createTrayController = ({ idleIconPath, unseenIconPath, breathIconPaths, statusIconPaths, onAction }) => {
  let tray = null;
  let lastTitle = null;

  // macOS auto-picks the @2x file next to each path and tints the alpha.
  const idleFrame = toTemplateImage(idleIconPath);
  const unseenFrame = toTemplateImage(unseenIconPath);
  const breathFrames = breathIconPaths.map(toTemplateImage);
  // Per-row status icons (template images, tinted + vertically centred by macOS).
  const statusIcons = {};
  for (const [key, p] of Object.entries(statusIconPaths || {})) {
    statusIcons[key] = toTemplateImage(p);
  }

  let iconState = null;
  let animTimer = null;
  let animIndex = 0;
  let animDir = 1;

  const stopAnim = () => {
    if (animTimer) {
      clearInterval(animTimer);
      animTimer = null;
    }
  };

  const startAnim = () => {
    if (animTimer || !tray || tray.isDestroyed?.()) return;
    animIndex = 0;
    animDir = 1;
    animTimer = setInterval(() => {
      if (!tray || tray.isDestroyed?.()) return;
      tray.setImage(breathFrames[animIndex] || idleFrame);
      // Ping-pong for a seamless, infinite in-and-out breath.
      animIndex += animDir;
      if (animIndex >= breathFrames.length - 1) { animIndex = breathFrames.length - 1; animDir = -1; }
      else if (animIndex <= 0) { animIndex = 0; animDir = 1; }
    }, ANIM_INTERVAL_MS);
  };

  const applyIconState = (nextState) => {
    if (nextState === iconState) return;
    iconState = nextState;
    if (!tray || tray.isDestroyed?.()) return;
    if (nextState === 'busy') {
      startAnim();
    } else if (nextState === 'unseen') {
      stopAnim();
      tray.setImage(unseenFrame);
    } else {
      stopAnim();
      tray.setImage(idleFrame);
    }
  };

  const ensureTray = () => {
    if (tray && !tray.isDestroyed?.()) return tray;
    tray = new Tray(idleFrame);
    tray.setIgnoreDoubleClickEvents(true);
    return tray;
  };

  const buildMenu = (snapshot) => {
    const sessions = Array.isArray(snapshot.sessions) ? snapshot.sessions : [];
    const approvals = Array.isArray(snapshot.approvals) ? snapshot.approvals : [];
    const header = typeof snapshot.instanceName === 'string' && snapshot.instanceName.trim()
      ? snapshot.instanceName.trim()
      : 'OpenChamber';

    const template = [
      { label: header, enabled: false },
      { type: 'separator' },
    ];

    if (approvals.length > 0) {
      template.push({ label: 'Needs your attention', enabled: false });
      const approvalItem = (approval) => {
        if (approval.kind === 'permission') {
          return {
            label: approvalLabel(approval),
            submenu: [
              { label: 'Allow once', click: () => onAction({ type: 'respond-permission', sessionId: approval.sessionId, id: approval.id, response: 'once' }) },
              { label: 'Allow always', click: () => onAction({ type: 'respond-permission', sessionId: approval.sessionId, id: approval.id, response: 'always' }) },
              { type: 'separator' },
              { label: 'Deny', click: () => onAction({ type: 'respond-permission', sessionId: approval.sessionId, id: approval.id, response: 'reject' }) },
              { type: 'separator' },
              { label: 'Open in app', click: () => onAction({ type: 'focus-session', sessionId: approval.sessionId, directory: approval.directory || '' }) },
            ],
          };
        }
        return {
          label: approvalLabel(approval),
          click: () => onAction({ type: 'focus-session', sessionId: approval.sessionId, directory: approval.directory || '' }),
        };
      };
      for (const approval of approvals.slice(0, MAX_APPROVALS)) {
        template.push(approvalItem(approval));
      }
      const approvalOverflow = approvals.slice(MAX_APPROVALS);
      if (approvalOverflow.length > 0) {
        template.push({
          label: `${approvalOverflow.length} more…`,
          submenu: approvalOverflow.map(approvalItem),
        });
      }
      template.push({ type: 'separator' });
    }

    const sessionItem = (session) => ({
      label: sessionLabel(session),
      // Status icon on the left, centred across both lines; idle uses the blank
      // placeholder so every row keeps the same gutter.
      icon: statusIcons[statusIconKey(session)] || statusIcons.blank,
      // Secondary smaller line (macOS): project · branch.
      ...(session.subtitle ? { sublabel: truncate(session.subtitle, 48) } : {}),
      click: () => onAction({ type: 'focus-session', sessionId: session.id, directory: session.directory || '' }),
    });

    if (sessions.length > 0) {
      template.push({ label: 'Sessions', enabled: false });
      for (const session of sessions.slice(0, MAX_SESSIONS)) {
        template.push(sessionItem(session));
      }
      const overflow = sessions.slice(MAX_SESSIONS);
      if (overflow.length > 0) {
        template.push({
          label: `${overflow.length} more…`,
          submenu: overflow.map(sessionItem),
        });
      }
    } else {
      template.push({ label: 'No active sessions', enabled: false });
    }

    // Usage submenu — only when the user has enabled providers for the dropdown
    // (same "configured to show" rule as the header/mobile); omitted otherwise.
    const usage = snapshot.usage && typeof snapshot.usage === 'object' ? snapshot.usage : null;
    const usageGroups = usage && Array.isArray(usage.groups) ? usage.groups : [];
    if (usageGroups.length > 0) {
      const modeLabel = usage.mode === 'remaining' ? 'Remaining' : 'Used';
      const usageSubmenu = [];
      usageGroups.forEach((group, index) => {
        if (index > 0) usageSubmenu.push({ type: 'separator' });
        // Read-only info rows. NSMenu only offers greyed-out for non-clickable
        // items (no custom text contrast), so these render dimmed — at the mercy
        // of macOS's menu contrast choices. Provider flush, rows indented.
        usageSubmenu.push({ label: group.provider, enabled: false });
        if (group.status) {
          usageSubmenu.push({ label: `    ${truncate(group.status, 40)}`, enabled: false });
        }
        for (const row of (Array.isArray(group.rows) ? group.rows : [])) {
          usageSubmenu.push({ label: `    ${row.label}  —  ${row.value}`, enabled: false });
        }
      });
      template.push(
        { type: 'separator' },
        { label: `Usage (${modeLabel})`, submenu: usageSubmenu },
      );
    }

    template.push(
      { type: 'separator' },
      { label: 'New Session', click: () => onAction({ type: 'new-session' }) },
      { label: 'New Mini Chat', click: () => onAction({ type: 'new-mini-chat' }) },
      { label: 'Show OpenChamber', click: () => onAction({ type: 'show-main-window' }) },
      { type: 'separator' },
      { label: 'Quit OpenChamber', click: () => onAction({ type: 'quit' }) },
    );

    return Menu.buildFromTemplate(template);
  };

  const update = (rawSnapshot) => {
    const snapshot = rawSnapshot && typeof rawSnapshot === 'object' ? rawSnapshot : {};
    const sessions = Array.isArray(snapshot.sessions) ? snapshot.sessions : [];
    const approvals = Array.isArray(snapshot.approvals) ? snapshot.approvals : [];

    const counts = {
      busy: sessions.filter((s) => s.status === 'busy' || s.status === 'retry').length,
      error: sessions.filter((s) => s.hasError).length,
      approvals: approvals.length,
      unseen: sessions.reduce((sum, s) => sum + (Number.isFinite(s.unseen) ? s.unseen : 0), 0),
    };

    const widget = ensureTray();
    const title = computeTitle(counts);
    if (title !== lastTitle) {
      widget.setTitle(title);
      lastTitle = title;
    }
    applyIconState(computeIconState(counts));
    widget.setToolTip(computeTooltip(counts, sessions.length));
    widget.setContextMenu(buildMenu(snapshot));
  };

  const destroy = () => {
    stopAnim();
    if (tray && !tray.isDestroyed?.()) {
      tray.destroy();
    }
    tray = null;
    lastTitle = null;
    iconState = null;
  };

  return { update, destroy };
};
