// ============================================================================
// extensions/email/commands/compose.ts
//
// T2.3: Compose & Send New — /triage send <to> <message>
// Flow: resolve identity → load context → draft with voice → gate → send
// ============================================================================

import { createRequire } from 'module';
import { existsSync, readFileSync } from 'node:fs';
import type { AccountConfig } from '../auth/accounts.ts';
import type { EmailProvider } from '../types.ts';
const require = createRequire(import.meta.url);
const { safeRead } = require('../../../lib/graph/graph-transport.js');
const { dataPath } = require('../../lib/elyntic-root.cjs');

export interface ComposeResult {
  success: boolean;
  draft?: {
    to: string;
    personName: string;
    platform: 'email' | 'imessage' | 'slack';
    subject?: string;
    body: string;
  };
  sent?: boolean;
  error?: string;
  gateBlocked?: boolean;
}

/** Host-owned send ports; tests substitute the external effect, not the command. */
export interface ComposeSendDependencies {
  gmail?: {
    accounts: AccountConfig[];
    createProvider(options: { account: AccountConfig }): Pick<EmailProvider, 'sendEmail'>;
  };
  sendIMessage?: (handle: string, text: string) => Promise<boolean>;
}

/**
 * /triage send <to> <message> — Compose and send a new message.
 * 
 * @param args - "Mike Hey can we sync today?" or "chiko@email.com Subject | Body"
 */
export async function runCompose(args: string): Promise<ComposeResult> {
  if (!args.trim()) {
    return { success: false, error: 'Usage: /triage send <name|handle> <message>' };
  }

  // Parse: first word is recipient, rest is message
  const parts = args.trim().split(/\s+/);
  const recipientStr = parts[0];
  const message = parts.slice(1).join(' ');

  if (!message) {
    return { success: false, error: 'Please provide a message after the recipient name.' };
  }

  // Resolve recipient
  const recipient = await resolveRecipient(recipientStr);
  if (!recipient) {
    return { success: false, error: `Could not find contact: "${recipientStr}"` };
  }

  // Determine subject (for email) — either extracted from message or auto-generated
  let subject: string | undefined;
  let body = message;

  if (recipient.platform === 'email') {
    // Check for "Subject | Body" pattern
    if (message.includes(' | ')) {
      const [subj, ...rest] = message.split(' | ');
      subject = subj;
      body = rest.join(' | ');
    } else {
      // Auto-generate subject from first few words
      subject = message.split(/[.!?]/)[0].slice(0, 60);
    }
  }

  // Return draft for governance gate approval (never auto-send)
  return {
    success: true,
    draft: {
      to: recipient.handle,
      personName: recipient.personName,
      platform: recipient.platform,
      subject,
      body,
    },
    sent: false, // Always requires gate approval
  };
}

/**
 * Execute the actual send (called after governance gate approval).
 */
export async function executeSend(draft: {
  to: string;
  platform: 'email' | 'imessage' | 'slack';
  subject?: string;
  body: string;
}, dependencies: ComposeSendDependencies = {}): Promise<{ sent: boolean; error?: string }> {
  try {
    switch (draft.platform) {
      case 'email': {
        const boundary = dependencies.gmail ?? await (async () => {
          const { GmailProvider } = await import('../providers/gmail.ts');
          const { getEnabledAccounts } = await import('../auth/accounts.ts');
          return { accounts: getEnabledAccounts('gmail'),
            createProvider: (options: { account: AccountConfig }) => new GmailProvider(options) };
        })();
        const accts = boundary.accounts;
        if (!accts.length) return { sent: false, error: 'No Gmail accounts configured' };
        const provider = boundary.createProvider({ account: accts[0] });
        const result = await provider.sendEmail({
          to: [draft.to],
          subject: draft.subject || '(no subject)',
          body: draft.body,
        });
        return { sent: result.sent };
      }

      case 'imessage': {
        const sendIMessage = dependencies.sendIMessage ?? (await import('../providers/imessage.ts')).sendIMessage;
        const success = await sendIMessage(draft.to, draft.body);
        return { sent: success };
      }

      case 'slack': {
        // The recipient resolver returns Slack identity handles, including user
        // IDs. chat.postMessage accepts a user ID as the channel for a DM.
        const configPath = dataPath('slack-config.json');
        let botToken: string | undefined;
        if (existsSync(configPath)) {
          try {
            const config = JSON.parse(readFileSync(configPath, 'utf-8'));
            botToken = config.botToken;
          } catch {
            // Preserve the existing environment fallback for a damaged config.
          }
        }
        botToken ||= process.env.SLACK_BOT_TOKEN;
        if (!botToken) return { sent: false, error: 'Slack not configured' };
        if (!draft.to.trim()) return { sent: false, error: 'Slack recipient missing' };

        const response = await fetch('https://slack.com/api/chat.postMessage', {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${botToken}`,
            'Content-Type': 'application/json; charset=utf-8',
          },
          body: JSON.stringify({ channel: draft.to, text: draft.body }),
          signal: AbortSignal.timeout(15000),
        });
        const result = await response.json() as { ok?: boolean; error?: string };
        if (!response.ok || result.ok !== true) {
          return { sent: false, error: result.error || `Slack API returned HTTP ${response.status}` };
        }
        return { sent: true };
      }

      default:
        return { sent: false, error: `Unsupported platform: ${draft.platform}` };
    }
  } catch (err: any) {
    return { sent: false, error: err.message };
  }
}

// --- Helper ---

async function resolveRecipient(identifier: string): Promise<{
  personId: string;
  personName: string;
  handle: string;
  platform: 'email' | 'imessage' | 'slack';
} | null> {
  // Email pattern
  if (identifier.includes('@')) {
    const cypher = `
      MATCH (i:Identity {handle: $handle})<-[:HAS_IDENTITY]-(p:Person)
      RETURN p.id AS personId, p.name AS personName, i.handle AS handle, i.platform AS platform
      LIMIT 1
    `;
    try {
      const results = await safeRead(cypher, { handle: identifier });
      if (results?.length) {
        return {
          personId: results[0].personId,
          personName: results[0].personName,
          handle: results[0].handle,
          platform: results[0].platform || 'email',
        };
      }
    } catch {}
    // Still valid even if not in graph
    return {
      personId: `unknown-${identifier}`,
      personName: identifier.split('@')[0],
      handle: identifier,
      platform: 'email',
    };
  }

  // Phone pattern
  if (identifier.match(/^\+?\d{10,}/)) {
    return {
      personId: `phone-${identifier}`,
      personName: identifier,
      handle: identifier,
      platform: 'imessage',
    };
  }

  // Name lookup
  const cypher = `
    MATCH (p:Person)
    WHERE toLower(p.name) CONTAINS toLower($name)
    MATCH (p)-[:HAS_IDENTITY]->(i:Identity)
    RETURN p.id AS personId, p.name AS personName, i.handle AS handle, i.platform AS platform
    ORDER BY p.totalInteractions DESC
    LIMIT 1
  `;

  try {
    const results = await safeRead(cypher, { name: identifier });
    if (results?.length) {
      return {
        personId: results[0].personId,
        personName: results[0].personName,
        handle: results[0].handle,
        platform: results[0].platform || 'email',
      };
    }
  } catch {}

  return null;
}
