/**
 * Forward Email – Demo Mode Manager
 *
 * Provides a complete sandboxed demo experience. When demo mode is active:
 *   1. All API requests are intercepted and served from fake data
 *   2. Write operations (send, move, delete, etc.) show a toast notification
 *      linking to https://forwardemail.net for sign-up
 *   3. The user can exit demo mode at any time
 *
 * Demo mode is activated via the "Try Demo" button on the Login page and
 * persisted in localStorage so it survives page reloads within the same session.
 */

import {
  DEMO_EMAIL,
  DEMO_STORAGE_KEY,
  generateFolders,
  generateMessages,
  generateContacts,
  generateCalendarEvents,
  generateAccountInfo,
  generateLabels,
} from './demo-data';
import { Local, Accounts } from './storage';

// ── State ─────────────────────────────────────────────────────────────────

let _active = false;
let _toasts = null;

// Track messages the user has marked as read during this demo session.
// Applied when generating demo data so read state survives re-fetches.
const _readMessageIds = new Set();

const SIGN_UP_URL = 'https://forwardemail.net';
const BLOCKED_MSG = 'This action isn’t available in the demo. Create an account to make changes.';

// Actions that are read-only and should return fake data
const READ_ACTIONS = new Set([
  'Folders',
  'FolderGet',
  'MessageList',
  'Message',
  'Contacts',
  'Calendars',
  'Calendar',
  'CalendarEvents',
  'Labels',
  'Account',
]);

// Write actions that are silently blocked (no toast) — background ops
// Note: MessageUpdate is handled explicitly above to track read state.
const SILENT_WRITE_ACTIONS = new Set();

// Actions that are write operations and should be blocked with toast
const WRITE_ACTIONS = new Set([
  'Emails',
  'EmailCancel',
  'FolderCreate',
  'FolderUpdate',
  'FolderDelete',
  'MessageCreate',
  'MessageDelete',
  'ContactsCreate',
  'ContactsUpdate',
  'ContactsDelete',
  'CalendarCreate',
  'CalendarUpdate',
  'CalendarDelete',
  'CalendarEventCreate',
  'CalendarEventUpdate',
  'CalendarEventDelete',
  'LabelsCreate',
  'LabelsUpdate',
  'AccountUpdate',
]);

// ── Public API ────────────────────────────────────────────────────────────

/**
 * Check if demo mode is currently active.
 */
export function isDemoMode() {
  if (_active) return true;
  try {
    return localStorage.getItem(DEMO_STORAGE_KEY) === '1';
  } catch {
    return false;
  }
}

/**
 * Activate demo mode. Sets up fake credentials in storage so the
 * rest of the app thinks a real user is logged in.
 */
export function activateDemoMode() {
  try {
    localStorage.setItem(DEMO_STORAGE_KEY, '1');
  } catch {
    // localStorage unavailable
  }

  _active = true;
}

/**
 * Deactivate demo mode and clean up all demo state.
 */
export function deactivateDemoMode() {
  _active = false;
  _readMessageIds.clear();
  try {
    localStorage.removeItem(DEMO_STORAGE_KEY);
  } catch {
    // ignore
  }
}

/**
 * Set the toast host reference so we can show notifications.
 * Called from main.ts after the toast host is created.
 */
export function setDemoToasts(toasts) {
  _toasts = toasts;
}

/**
 * Return true when a failed mutation was intentionally blocked by demo mode.
 * Callers use this to avoid showing a second, generic failure message after
 * the central demo notice has already explained what happened.
 */
export function isDemoBlockedError(error) {
  return Boolean(error && (error.isDemo === true || error.code === 'DEMO_ACTION_BLOCKED'));
}

/**
 * Show the "not available in demo" toast with a sign-up action button.
 * If the user clicks the action, we log them out and open the sign-up page.
 */
export function showDemoBlockedToast(actionLabel) {
  if (!_toasts) {
    console.warn('[demo] Toast host not available');
    return;
  }

  const label = actionLabel
    ? `${actionLabel} isn’t available in the demo. Create an account to make changes.`
    : BLOCKED_MSG;

  _toasts.show(label, 'warning', {
    duration: 12000,
    action: {
      label: 'Create Account',
      callback: () => {
        exitDemoAndRedirect();
      },
    },
  });
}

/**
 * Silently clean up all demo account state.
 * Use this when transitioning from demo to a real account (login or bootstrap).
 * Unlike exitDemoAndRedirect(), this does NOT open the sign-up page or reload.
 *
 * @param {object} [options]
 * @param {boolean} [options.preserveCredentials=false] - When true, only remove
 *   the demo Accounts entry and deactivate demo mode without touching
 *   Local/Session credential keys.  Use this during bootstrap when a real
 *   account's credentials are already stored in those shared keys.
 * @returns {Promise<void>}
 */
export async function cleanupDemoAccount({ preserveCredentials = false } = {}) {
  deactivateDemoMode();

  // Remove demo account entry and its IndexedDB cache
  try {
    await Accounts.remove(DEMO_EMAIL, true);
  } catch {
    // Best effort — Accounts.remove may reject if entry is already gone
  }

  // Only clear shared credential keys when the caller knows they still
  // hold demo values (e.g. during login before the real account is set up).
  // During bootstrap the real account's credentials are already stored in
  // these keys, so wiping them would break the active session.
  if (!preserveCredentials) {
    try {
      Local.remove('email');
      Local.remove('alias_auth');
      Local.remove('api_token');
    } catch {
      // ignore
    }

    try {
      sessionStorage.removeItem('webmail_email');
      sessionStorage.removeItem('webmail_alias_auth');
      sessionStorage.removeItem('webmail_api_key');
      sessionStorage.removeItem('webmail_authToken');
      sessionStorage.removeItem('webmail_active_account');
      sessionStorage.removeItem('webmail_session_accounts');
    } catch {
      // ignore
    }
  }
}

/**
 * Exit demo mode, clear credentials, and redirect to sign-up page.
 */
export function exitDemoAndRedirect() {
  deactivateDemoMode();

  // Clear demo credentials from storage
  try {
    Accounts.remove(DEMO_EMAIL);
    Local.remove('email');
    Local.remove('alias_auth');
    Local.remove('api_token');
  } catch {
    // Best effort cleanup
  }

  // Clear tab-scoped session keys
  try {
    sessionStorage.clear();
  } catch {
    // ignore
  }

  // Open sign-up page
  window.open(SIGN_UP_URL, '_blank', 'noopener,noreferrer');

  // Hard navigation to login with replace to clear the current history entry
  window.location.replace('/');
}

/**
 * Intercept a Remote.request() call in demo mode.
 * Returns { handled: true, result: ... } if we handled it,
 * or { handled: false } if the real API should be called.
 */
export function interceptDemoRequest(action, params = {}, options = {}) {
  if (!isDemoMode()) return { handled: false };

  // Extract message ID from pathOverride if present (e.g. /v1/messages/demo-1?folder=INBOX)
  if (action === 'Message' && options?.pathOverride) {
    const match = options.pathOverride.match(/\/v1\/messages\/([^?]+)/);
    if (match) params = { ...params, id: decodeURIComponent(match[1]) };
    const folderMatch = options.pathOverride.match(/folder=([^&]+)/);
    if (folderMatch) params = { ...params, folder: decodeURIComponent(folderMatch[1]) };
  }

  // Handle read actions with fake data
  if (READ_ACTIONS.has(action)) {
    return { handled: true, result: getDemoData(action, params) };
  }

  // MessageUpdate is overloaded: a flag change (read/star toggle) carries
  // `flags`, while a move/delete-to-trash carries only `folder`. Flag
  // changes are allowed silently in demo (so badge + bold state stay
  // consistent across re-fetches); folder moves are destructive writes
  // and, per the demo contract ("move, delete show a toast"), must be
  // blocked with the sign-up toast rather than silently pretend-applied.
  if (action === 'MessageUpdate') {
    const flags = params?.flags || params?.addFlags || [];
    const isFlagUpdate = Array.isArray(flags) && flags.length > 0;
    if (isFlagUpdate) {
      if (flags.includes('\\Seen') && params?.id) {
        _readMessageIds.add(params.id);
      }
      return { handled: true, result: { ok: true, demo: true } };
    }
    if (params?.folder) {
      // Folder change with no flags ⇒ a move. Callers that expose a more
      // specific user action (for example delete-to-trash or archive) can
      // provide a demo-only label in request options; Remote does not forward
      // this option to the network transport outside demo mode.
      showDemoBlockedToast(options?.demoAction || 'Move');
      return { handled: true, result: { ok: false, demo: true, blocked: true } };
    }
    return { handled: true, result: { ok: true, demo: true } };
  }

  // Silently block other background write actions (no toast, no error)
  if (SILENT_WRITE_ACTIONS.has(action)) {
    return { handled: true, result: { ok: true, demo: true } };
  }

  // Block write actions with toast. Callers using a shared endpoint can
  // provide a more specific, demo-only action label; Remote never forwards
  // this option to the network transport outside demo mode.
  if (WRITE_ACTIONS.has(action)) {
    const friendlyName = options?.demoAction || getFriendlyActionName(action);
    showDemoBlockedToast(friendlyName);
    // Return handled with a special demo marker so Remote.request can
    // return without making a real API call.  The toast is the feedback.
    return { handled: true, result: { ok: false, demo: true, blocked: true } };
  }

  // Unknown action — let it through (it will likely fail with fake auth,
  // which is fine since the user is in demo mode)
  return { handled: false };
}

// ── Data Generators ───────────────────────────────────────────────────────

function getDemoData(action, params) {
  switch (action) {
    case 'Folders': {
      const rawFolders = generateFolders();
      // Recompute unseen counts based on tracked read state
      return recomputeFolderCounts(rawFolders);
    }

    case 'FolderGet': {
      const folders = recomputeFolderCounts(generateFolders());
      const id = params?.id || params?.path;
      return folders.find((f) => f.id === id || f.path === id) || folders[0];
    }

    case 'MessageList':
    case 'Message': {
      const folder = params?.folder || params?.mailbox || params?.path || 'INBOX';
      const page = Number(params?.page) || 1;
      const messages = applyReadState(generateMessages(folder, page));
      if (action === 'Message' && params?.id) {
        return messages.find((m) => m.id === params.id) || messages[0] || null;
      }

      // Return in the format expected by mailboxStore.ts:
      // source === 'main' path reads: res?.Result?.List || res?.Result || res || []
      // So we return the array directly so `res || []` gives the array.
      return messages;
    }

    case 'Contacts':
      return generateContacts();

    case 'Calendars':
    case 'Calendar':
      return [
        { id: 'demo-calendar', name: 'Calendar', color: '#3b82f6', description: 'Main calendar' },
        { id: 'demo-tasks', name: 'Tasks', color: '#3b82f6', description: 'Task list' },
        { id: 'demo-reminders', name: 'Reminders', color: '#3b82f6', description: 'Reminders' },
      ];

    case 'CalendarEvents':
      return generateCalendarEvents();

    case 'Labels':
      return generateLabels();

    case 'Account':
      return generateAccountInfo();

    default:
      return null;
  }
}

/**
 * Apply tracked read state to generated messages.
 * Adds \Seen flag to any message the user has read during this session.
 */
function applyReadState(messages) {
  return messages.map((msg) => {
    if (_readMessageIds.has(msg.id) && !msg.flags?.includes('\\Seen')) {
      return { ...msg, flags: [...(msg.flags || []), '\\Seen'] };
    }
    return msg;
  });
}

/**
 * Recompute folder unseen counts by applying read state to all messages
 * in each folder and counting those without \Seen.
 */
function recomputeFolderCounts(folders) {
  if (_readMessageIds.size === 0) return folders;
  return folders.map((folder) => {
    const msgs = applyReadState(generateMessages(folder.path, 1));
    const unseen = msgs.filter((m) => !m.flags?.includes('\\Seen')).length;
    return { ...folder, unseen };
  });
}

function getFriendlyActionName(action) {
  const names = {
    Emails: 'Send email',
    EmailCancel: 'Cancel email',
    FolderCreate: 'Create folder',
    FolderUpdate: 'Update folder',
    FolderDelete: 'Delete folder',
    MessageCreate: 'Save draft',
    MessageUpdate: 'Update message',
    MessageDelete: 'Delete message',
    ContactsCreate: 'Create contact',
    ContactsUpdate: 'Update contact',
    ContactsDelete: 'Delete contact',
    CalendarCreate: 'Create calendar',
    CalendarUpdate: 'Update calendar',
    CalendarDelete: 'Delete calendar',
    CalendarEventCreate: 'Create event',
    CalendarEventUpdate: 'Update event',
    CalendarEventDelete: 'Delete event',
    LabelsCreate: 'Create label',
    LabelsUpdate: 'Update label',
    AccountUpdate: 'Update account',
  };
  return names[action] || action;
}
