import { z } from 'zod';
import { BasePack } from '../base-pack.js';
import * as renderer from '../../apps/renderer.js';
import { buildTwiML, sayTwiML } from '../../utils/twiml-builder.js';
import {
  buildFlow,
  triggerWidget,
  sayWidget,
  gatherWidget,
  connectWidget,
  splitWidget,
  recordWidget,
  sendMessageWidget,
} from '../../utils/studio-builder.js';
import {
  toE164,
  callStatusInfo,
  formatDuration,
  timeAgo,
} from '../../utils/validators.js';

export class VoicePack extends BasePack {
  readonly name = 'voice';
  readonly description = 'Voice call tools: IVR, call blasts, voicemail, conferencing, analytics';
  readonly keywords = ['call', 'voice', 'ivr', 'phone', 'dial', 'conference', 'voicemail', 'greeting'];

  /** Map status info colors to badge variants */
  private mapColorToBadge(color: 'green' | 'yellow' | 'red' | 'blue' | 'gray'): 'success' | 'warning' | 'danger' | 'info' | 'muted' {
    const map: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'muted'> = {
      green: 'success',
      yellow: 'warning',
      red: 'danger',
      blue: 'info',
      gray: 'muted',
    };
    return map[color] || 'muted';
  }

  setup(): void {
    // ─── twilio_build_ivr ───
    this.registerTool({
      name: 'twilio_build_ivr',
      description: 'Build and deploy a complete IVR (Interactive Voice Response) menu system. Creates a Studio flow with greeting → digit collection → routing. The hero tool for voice automation.',
      category: 'voice',
      safety: 'yellow',
      tier: 2,
      isApp: true,
      inputSchema: z.object({
        number: z.string().describe('Phone number to assign this IVR to'),
        menu: z.array(z.object({
          digit: z.string().describe('Digit to press (0-9, *, #)'),
          label: z.string().describe('Human-readable label for this option'),
          action: z.enum(['connect', 'say', 'voicemail', 'queue']).describe('What happens when this digit is pressed'),
          value: z.string().describe('Phone number (for connect), message (for say), queue name (for queue)'),
        })).describe('Menu options array'),
        greeting: z.string().describe('Greeting message to play before the menu'),
        voice: z.string().optional().describe('Voice to use (default: Polly.Amy)'),
        no_input_message: z.string().optional().describe('Message to play if user doesn\'t press anything'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "write",
          complexity: "simple"
        }
      },
      handler: async (params) => this.buildIVR(params),
    });

    // ─── twilio_call_blast ───
    this.registerTool({
      name: 'twilio_call_blast',
      description: 'Place outbound calls to multiple recipients with a text-to-speech message. Track completion status for each recipient. Perfect for announcements and alerts.',
      category: 'voice',
      safety: 'red',
      tier: 2,
      isApp: true,
      annotations: { costHint: true, estimatedCost: '~$0.014/min per call (US)' },
      inputSchema: z.object({
        recipients: z.array(z.string()).describe('Array of phone numbers to call'),
        message: z.string().describe('Text message to speak when answered'),
        voice: z.string().optional().describe('Voice to use (default: Polly.Amy)'),
        from: z.string().optional().describe('Caller ID number (auto-selected if omitted)'),
        retry_unanswered: z.boolean().optional().describe('Whether to retry calls that aren\'t answered'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "delete",
          complexity: "simple"
        }
      },
      handler: async (params) => this.callBlast(params),
    });

    // ─── twilio_record_greeting ───
    this.registerTool({
      name: 'twilio_record_greeting',
      description: 'Call a number and record a custom greeting/message. Perfect for creating voicemail greetings, IVR prompts, and hold music.',
      category: 'voice',
      safety: 'yellow',
      tier: 2,
      annotations: { costHint: true, estimatedCost: '~$0.014/min (US)' },
      inputSchema: z.object({
        call_to: z.string().describe('Phone number to call for recording'),
        save_as: z.string().describe('Friendly name for this recording'),
        max_duration: z.number().optional().describe('Maximum recording duration in seconds (default: 30)'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => this.recordGreeting(params),
    });

    // ─── twilio_voicemail_box ───
    this.registerTool({
      name: 'twilio_voicemail_box',
      description: 'Set up a complete voicemail system for a phone number. Plays greeting, records message, optionally transcribes and forwards via SMS.',
      category: 'voice',
      safety: 'yellow',
      tier: 2,
      inputSchema: z.object({
        number: z.string().describe('Phone number to enable voicemail on'),
        greeting_text: z.string().optional().describe('Text-to-speech greeting (e.g., "Leave a message after the beep")'),
        greeting_url: z.string().optional().describe('URL of pre-recorded audio greeting (overrides greeting_text)'),
        transcribe: z.boolean().optional().describe('Whether to transcribe voicemails (default: true)'),
        forward_to: z.string().optional().describe('Phone number to SMS transcript to'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => this.voicemailBox(params),
    });

    // ─── twilio_conference_room ───
    this.registerTool({
      name: 'twilio_conference_room',
      description: 'Create a conference call room and optionally dial participants into it. Supports recording, moderators, and custom settings.',
      category: 'voice',
      safety: 'yellow',
      tier: 2,
      annotations: { costHint: true, estimatedCost: '~$0.014/min per participant (US)' },
      inputSchema: z.object({
        name: z.string().describe('Conference room name/identifier'),
        participants: z.array(z.string()).optional().describe('Phone numbers to dial into the conference'),
        moderator: z.string().optional().describe('Phone number of moderator (starts conference)'),
        record: z.boolean().optional().describe('Whether to record the conference'),
        max_participants: z.number().optional().describe('Maximum participants (default: 10)'),
        from: z.string().optional().describe('Caller ID for outbound dials'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => this.conferenceRoom(params),
    });

    // ─── twilio_call_analytics ───
    this.registerTool({
      name: 'twilio_call_analytics',
      description: 'Analyze call activity with filters and visualize metrics: total calls, average duration, answer rate, costs. Returns an interactive dashboard.',
      category: 'voice',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({
        date_start: z.string().optional().describe('Start date (ISO format, e.g., 2024-01-01)'),
        date_end: z.string().optional().describe('End date (ISO format)'),
        number: z.string().optional().describe('Filter by specific phone number'),
        direction: z.enum(['inbound', 'outbound']).optional().describe('Filter by call direction'),
        limit: z.number().optional().describe('Maximum calls to analyze (default: 50)'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => this.callAnalytics(params),
    });

    // ─── twilio_call_transcribe ───
    this.registerTool({
      name: 'twilio_call_transcribe',
      description: 'Get transcription and recording details for a specific call. Returns text transcription, recording URL, and metadata.',
      category: 'voice',
      safety: 'green',
      tier: 2,
      annotations: { readOnlyHint: true, costHint: true, estimatedCost: '~$0.05 per transcription' },
      inputSchema: z.object({
        call_sid: z.string().describe('Call SID to retrieve transcription for'),
      }),
      _meta: {
        labels: {
          category: "calls",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => this.callTranscribe(params),
    });
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // TOOL IMPLEMENTATIONS
  // ═══════════════════════════════════════════════════════════════════════════

  private async buildIVR(params: {
    number: string;
    menu: Array<{ digit: string; label: string; action: 'connect' | 'say' | 'voicemail' | 'queue'; value: string }>;
    greeting: string;
    voice?: string;
    no_input_message?: string;
  }) {
    try {
      const phoneNumber = toE164(params.number);
      const voice = params.voice || 'Polly.Amy';
      const noInputMsg = params.no_input_message || 'Sorry, I didn\'t receive any input. Goodbye.';

      // Build Studio flow widgets
      const widgets = [
        triggerWidget({ incomingCall: 'say_greeting' }),
        sayWidget('say_greeting', params.greeting, { voice, next: 'gather_input' }),
        gatherWidget('gather_input', 'Please press a digit.', {
          numDigits: 1,
          timeout: 5,
          voice,
          onComplete: 'route_digit',
          onTimeout: 'no_input',
        }),
      ];

      // Create split widget for routing
      const conditions = params.menu.map(item => ({
        value: item.digit,
        next: `action_${item.digit}`,
      }));

      widgets.push(
        splitWidget('route_digit', '{{widgets.gather_input.Digits}}', conditions, { noMatch: 'no_input' })
      );

      // Create action widgets for each menu item
      for (const item of params.menu) {
        switch (item.action) {
          case 'connect':
            widgets.push(
              sayWidget(`action_${item.digit}`, `Connecting you to ${item.label}.`, { voice, next: `connect_${item.digit}` }),
              connectWidget(`connect_${item.digit}`, toE164(item.value))
            );
            break;
          case 'say':
            widgets.push(
              sayWidget(`action_${item.digit}`, item.value, { voice })
            );
            break;
          case 'voicemail':
            widgets.push(
              sayWidget(`action_${item.digit}`, item.value, { voice, next: `record_${item.digit}` }),
              recordWidget(`record_${item.digit}`, { transcribe: true })
            );
            break;
          case 'queue':
            // For queue, we'd use enqueue widget (simplified here as say)
            widgets.push(
              sayWidget(`action_${item.digit}`, `Adding you to the ${item.value} queue.`, { voice })
            );
            break;
        }
      }

      // No input handler
      widgets.push(sayWidget('no_input', noInputMsg, { voice }));

      const flow = buildFlow(`IVR Menu for ${phoneNumber}`, widgets);

      // Create the flow via API
      const createdFlow: any = await this.client.execute(c =>
        (c as any).studio.v2.flows.create({
          friendlyName: `IVR - ${phoneNumber}`,
          status: 'published',
          definition: flow,
        })
      );

      // Assign flow to the phone number
      const numbers = await this.client.execute(c =>
        c.incomingPhoneNumbers.list({ phoneNumber })
      );

      if (numbers.length === 0) {
        return this.errorResult(`Phone number ${phoneNumber} not found in your account`);
      }

      await this.client.execute(c =>
        c.incomingPhoneNumbers(numbers[0].sid).update({
          voiceUrl: `https://webhooks.twilio.com/v1/Accounts/${this.client.accountSid}/Flows/${createdFlow.sid}`,
        })
      );

      // Build the app visualization
      const menuItems = params.menu.map(item =>
        `<div class="list-item">
          <div style="font-size: 20px; font-weight: 700; color: #6366f1; min-width: 40px;">${item.digit}</div>
          <div style="flex: 1;">
            <div style="font-weight: 600;">${item.label}</div>
            <div class="muted small">${item.action}: ${item.value}</div>
          </div>
          ${renderer.badge(item.action, item.action === 'connect' ? 'info' : 'success')}
        </div>`
      ).join('');

      const html = renderer.appLayout(
        '📞 IVR Menu Deployed',
        `Active on ${phoneNumber}`,
        `
        <div class="card">
          <div class="card-header">Flow Structure</div>
          <div style="padding: 12px 0;">
            <div class="flow-node active">📞 Incoming Call</div>
            <span class="flow-arrow">→</span>
            <div class="flow-node">🗣️ Greeting</div>
            <span class="flow-arrow">→</span>
            <div class="flow-node">⌨️ Collect Digit</div>
            <span class="flow-arrow">→</span>
            <div class="flow-node">🔀 Route</div>
          </div>
        </div>

        <div class="card">
          <div class="card-header">Menu Options</div>
          ${menuItems}
        </div>

        <div class="card">
          <div class="card-header">Configuration</div>
          <table style="width: 100%; font-size: 12px;">
            <tr><td style="padding: 4px 0; color: #94a3b8;">Flow SID:</td><td style="padding: 4px 0;">${createdFlow.sid}</td></tr>
            <tr><td style="padding: 4px 0; color: #94a3b8;">Voice:</td><td style="padding: 4px 0;">${voice}</td></tr>
            <tr><td style="padding: 4px 0; color: #94a3b8;">Timeout Message:</td><td style="padding: 4px 0;">${noInputMsg}</td></tr>
          </table>
        </div>
        `
      );

      return this.appResult(
        html,
        `IVR deployed! Flow ${createdFlow.sid} assigned to ${phoneNumber}. ` +
        `Menu: ${params.menu.map(m => `${m.digit}=${m.label}`).join(', ')}`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to build IVR: ${err.message}`);
    }
  }

  private async callBlast(params: {
    recipients: string[];
    message: string;
    voice?: string;
    from?: string;
    retry_unanswered?: boolean;
  }) {
    try {
      const voice = params.voice || 'Polly.Amy';
      const twiml = sayTwiML(params.message, voice);

      // Get from number
      let from = params.from;
      if (!from) {
        const numbers = await this.client.execute(c =>
          c.incomingPhoneNumbers.list({ limit: 1 })
        );
        if (numbers.length === 0) {
          return this.errorResult('No phone numbers on this account');
        }
        from = numbers[0].phoneNumber;
      }

      // Place calls
      const results: Array<{ to: string; sid: string; status: string; error?: string }> = [];

      for (const recipient of params.recipients) {
        try {
          const to = toE164(recipient);
          const call = await this.client.execute(c =>
            c.calls.create({ to, from: toE164(from!), twiml })
          );
          results.push({ to, sid: call.sid, status: call.status });
        } catch (err: any) {
          results.push({ to: recipient, sid: '', status: 'failed', error: err.message });
        }
      }

      // Wait a bit for status updates
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Fetch updated statuses
      for (const result of results) {
        if (result.sid) {
          try {
            const call = await this.client.execute(c => c.calls(result.sid).fetch());
            result.status = call.status;
          } catch {
            // Keep original status
          }
        }
      }

      // Compute stats
      const total = results.length;
      const answered = results.filter(r => r.status === 'completed' || r.status === 'in-progress').length;
      const noAnswer = results.filter(r => r.status === 'no-answer').length;
      const failed = results.filter(r => r.status === 'failed' || r.error).length;

      // Build app
      const tableRows = results.map(r => {
        const statusInfo = callStatusInfo(r.status);
        return [
          r.to,
          renderer.badge(statusInfo.label, this.mapColorToBadge(statusInfo.color)),
          r.sid || '—',
          r.error || '—',
        ];
      });

      const html = renderer.appLayout(
        '📣 Call Blast Campaign',
        `Message: "${params.message.slice(0, 60)}${params.message.length > 60 ? '...' : ''}"`,
        `
        <div class="card-grid card-grid-4">
          ${renderer.metricCard(String(total), 'Total Calls')}
          ${renderer.metricCard(String(answered), 'Answered')}
          ${renderer.metricCard(String(noAnswer), 'No Answer')}
          ${renderer.metricCard(String(failed), 'Failed')}
        </div>

        <div class="card">
          <div class="card-header">Recipients</div>
          ${renderer.table(['Number', 'Status', 'Call SID', 'Error'], tableRows)}
        </div>
        `
      );

      return this.appResult(
        html,
        `Call blast complete: ${answered}/${total} answered, ${noAnswer} no-answer, ${failed} failed`
      );
    } catch (err: any) {
      return this.errorResult(`Call blast failed: ${err.message}`);
    }
  }

  private async recordGreeting(params: {
    call_to: string;
    save_as: string;
    max_duration?: number;
  }) {
    try {
      const to = toE164(params.call_to);
      const maxDuration = params.max_duration || 30;

      // Get from number
      const numbers = await this.client.execute(c =>
        c.incomingPhoneNumbers.list({ limit: 1 })
      );
      if (numbers.length === 0) {
        return this.errorResult('No phone numbers on this account');
      }
      const from = numbers[0].phoneNumber;

      // Build TwiML for recording
      const twiml = buildTwiML([
        { type: 'say', message: `Hello! Please record your greeting for ${params.save_as} after the beep. Press any key when finished.`, voice: 'Polly.Amy' },
        { type: 'record', timeout: maxDuration, action: `https://demo.twilio.com/welcome/voice/`, method: 'POST' },
        { type: 'say', message: 'Thank you. Your greeting has been recorded. Goodbye.' },
        { type: 'hangup' },
      ]);

      const call = await this.client.execute(c =>
        c.calls.create({ to, from, twiml })
      );

      // Wait for call to complete and recording to be available
      await new Promise(resolve => setTimeout(resolve, 3000));

      // Fetch recordings for this call
      const recordings = await this.client.execute(c =>
        c.recordings.list({ callSid: call.sid, limit: 1 })
      );

      if (recordings.length === 0) {
        return this.textResult(
          `Recording call initiated!\n` +
          `  Call SID: ${call.sid}\n` +
          `  Status: ${call.status}\n` +
          `  Name: ${params.save_as}\n\n` +
          `The recording will be available shortly. Check back with call SID: ${call.sid}`
        );
      }

      const recording = recordings[0];
      const recordingUrl = `https://api.twilio.com${recording.uri.replace('.json', '.mp3')}`;

      return this.textResult(
        `Recording created!\n` +
        `  Name: ${params.save_as}\n` +
        `  Recording SID: ${recording.sid}\n` +
        `  Duration: ${recording.duration} seconds\n` +
        `  URL: ${recordingUrl}\n` +
        `  Call SID: ${call.sid}`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to record greeting: ${err.message}`);
    }
  }

  private async voicemailBox(params: {
    number: string;
    greeting_text?: string;
    greeting_url?: string;
    transcribe?: boolean;
    forward_to?: string;
  }) {
    try {
      const phoneNumber = toE164(params.number);
      const transcribe = params.transcribe !== false;
      const greetingText = params.greeting_text || 'Please leave a message after the beep.';

      // Build Studio flow
      const widgets = [
        triggerWidget({ incomingCall: 'greeting' }),
      ];

      // Greeting
      if (params.greeting_url) {
        widgets.push({
          name: 'greeting',
          type: 'say-play',
          transitions: [{ event: 'audioComplete', next: 'record' }],
          properties: { play: params.greeting_url },
        });
      } else {
        widgets.push(sayWidget('greeting', greetingText, { voice: 'Polly.Amy', next: 'record' }));
      }

      // Record
      widgets.push(recordWidget('record', {
        transcribe,
        maxLength: 120,
        onComplete: params.forward_to ? 'send_sms' : undefined,
      }));

      // Optional: forward transcript via SMS
      if (params.forward_to) {
        widgets.push(
          sendMessageWidget('send_sms', 'New voicemail: {{widgets.record.RecordingUrl}}\nTranscript: {{widgets.record.TranscriptionText}}', {
            to: toE164(params.forward_to),
          })
        );
      }

      const flow = buildFlow(`Voicemail for ${phoneNumber}`, widgets);

      // Create flow
      const createdFlow: any = await this.client.execute(c =>
        (c as any).studio.v2.flows.create({
          friendlyName: `Voicemail - ${phoneNumber}`,
          status: 'published',
          definition: flow,
        })
      );

      // Assign to number
      const numbers = await this.client.execute(c =>
        c.incomingPhoneNumbers.list({ phoneNumber })
      );

      if (numbers.length === 0) {
        return this.errorResult(`Phone number ${phoneNumber} not found`);
      }

      await this.client.execute(c =>
        c.incomingPhoneNumbers(numbers[0].sid).update({
          voiceUrl: `https://webhooks.twilio.com/v1/Accounts/${this.client.accountSid}/Flows/${createdFlow.sid}`,
        })
      );

      return this.textResult(
        `Voicemail box configured!\n` +
        `  Phone: ${phoneNumber}\n` +
        `  Flow SID: ${createdFlow.sid}\n` +
        `  Transcription: ${transcribe ? 'Enabled' : 'Disabled'}\n` +
        `  Forward to: ${params.forward_to || 'None'}\n` +
        `  Greeting: ${params.greeting_url ? 'Custom audio' : 'Text-to-speech'}`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to create voicemail box: ${err.message}`);
    }
  }

  private async conferenceRoom(params: {
    name: string;
    participants?: string[];
    moderator?: string;
    record?: boolean;
    max_participants?: number;
    from?: string;
  }) {
    try {
      const conferenceName = params.name;
      const record = params.record || false;

      // Get from number
      let from = params.from;
      if (!from) {
        const numbers = await this.client.execute(c =>
          c.incomingPhoneNumbers.list({ limit: 1 })
        );
        if (numbers.length === 0) {
          return this.errorResult('No phone numbers on this account');
        }
        from = numbers[0].phoneNumber;
      }

      // Build TwiML for conference
      const conferenceTwiML = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Welcome to the ${conferenceName} conference.</Say>
  <Dial>
    <Conference${record ? ' record="record-from-start"' : ''}>${conferenceName}</Conference>
  </Dial>
</Response>`;

      // Dial participants
      const dialedParticipants: string[] = [];
      if (params.participants && params.participants.length > 0) {
        for (const participant of params.participants) {
          try {
            await this.client.execute(c =>
              c.calls.create({
                to: toE164(participant),
                from: toE164(from!),
                twiml: conferenceTwiML,
              })
            );
            dialedParticipants.push(participant);
          } catch (err: any) {
            // Continue with other participants
          }
        }
      }

      return this.textResult(
        `Conference room created!\n` +
        `  Name: ${conferenceName}\n` +
        `  Recording: ${record ? 'Enabled' : 'Disabled'}\n` +
        `  Participants dialed: ${dialedParticipants.length}\n` +
        `  ${dialedParticipants.join(', ')}\n\n` +
        `To join manually, call ${from} and use TwiML:\n${conferenceTwiML}`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to create conference: ${err.message}`);
    }
  }

  private async callAnalytics(params: {
    date_start?: string;
    date_end?: string;
    number?: string;
    direction?: 'inbound' | 'outbound';
    limit?: number;
  }) {
    try {
      const limit = params.limit || 50;
      const queryParams: any = { limit };

      if (params.date_start) queryParams.startTimeAfter = new Date(params.date_start);
      if (params.date_end) queryParams.startTimeBefore = new Date(params.date_end);
      if (params.number) queryParams.to = toE164(params.number);

      const calls = await this.client.execute(c => c.calls.list(queryParams));

      // Filter by direction if specified
      let filteredCalls = calls;
      if (params.direction) {
        filteredCalls = calls.filter(c => c.direction === `${params.direction}-api` || c.direction === params.direction);
      }

      // Compute metrics
      const totalCalls = filteredCalls.length;
      const completedCalls = filteredCalls.filter(c => c.status === 'completed').length;
      const totalDuration = filteredCalls.reduce((acc, c) => acc + parseInt(c.duration || '0'), 0);
      const avgDuration = totalCalls > 0 ? Math.round(totalDuration / totalCalls) : 0;
      const answerRate = totalCalls > 0 ? Math.round((completedCalls / totalCalls) * 100) : 0;
      const totalCost = filteredCalls.reduce((acc, c) => {
        const price = parseFloat(c.price || '0');
        return acc + Math.abs(price);
      }, 0);

      // Build table
      const tableRows = filteredCalls.slice(0, 50).map(c => {
        const statusInfo = callStatusInfo(c.status);
        const cost = c.price ? `$${Math.abs(parseFloat(c.price)).toFixed(4)}` : '—';
        return [
          renderer.badge(c.direction === 'inbound' ? 'In' : 'Out', c.direction === 'inbound' ? 'info' : 'success'),
          c.from || '—',
          c.to || '—',
          formatDuration(parseInt(c.duration || '0')),
          renderer.badge(statusInfo.label, this.mapColorToBadge(statusInfo.color)),
          cost,
          timeAgo(c.dateCreated!),
        ];
      });

      const html = renderer.appLayout(
        '📊 Call Analytics',
        params.date_start || params.date_end
          ? `${params.date_start || 'start'} to ${params.date_end || 'now'}`
          : `Last ${limit} calls`,
        `
        <div class="card-grid card-grid-4">
          ${renderer.metricCard(String(totalCalls), 'Total Calls')}
          ${renderer.metricCard(formatDuration(avgDuration), 'Avg Duration')}
          ${renderer.metricCard(`${answerRate}%`, 'Answer Rate')}
          ${renderer.metricCard(`$${totalCost.toFixed(2)}`, 'Total Cost')}
        </div>

        <div class="card">
          <div class="card-header">Call Log</div>
          ${renderer.table(
            ['Dir', 'From', 'To', 'Duration', 'Status', 'Cost', 'Date'],
            tableRows
          )}
        </div>
        `
      );

      return this.appResult(
        html,
        `Call Analytics: ${totalCalls} calls, ${formatDuration(avgDuration)} avg duration, ${answerRate}% answer rate, $${totalCost.toFixed(2)} total cost`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to fetch call analytics: ${err.message}`);
    }
  }

  private async callTranscribe(params: { call_sid: string }) {
    try {
      // Fetch the call
      const call = await this.client.execute(c => c.calls(params.call_sid).fetch());

      // Fetch recordings
      const recordings = await this.client.execute(c =>
        c.recordings.list({ callSid: params.call_sid })
      );

      if (recordings.length === 0) {
        return this.textResult(
          `No recordings found for call ${params.call_sid}.\n` +
          `  Status: ${call.status}\n` +
          `  Duration: ${call.duration || 'N/A'} seconds`
        );
      }

      const recording = recordings[0];
      const recordingUrl = `https://api.twilio.com${recording.uri.replace('.json', '.mp3')}`;

      // Fetch transcriptions
      const transcriptions: any[] = await this.client.execute(c =>
        (c as any).transcriptions.list({ recordingSid: recording.sid })
      );

      if (transcriptions.length === 0) {
        return this.textResult(
          `Recording found but no transcription yet.\n` +
          `  Recording SID: ${recording.sid}\n` +
          `  Duration: ${recording.duration} seconds\n` +
          `  URL: ${recordingUrl}\n\n` +
          `Transcription may still be processing. Try again in a minute.`
        );
      }

      const transcription = transcriptions[0];

      return this.textResult(
        `Call Transcription\n` +
        `═══════════════════\n` +
        `Call SID: ${params.call_sid}\n` +
        `Recording SID: ${recording.sid}\n` +
        `Duration: ${recording.duration} seconds\n` +
        `Recording URL: ${recordingUrl}\n\n` +
        `Transcription:\n` +
        `${transcription.transcriptionText || 'No text available'}\n\n` +
        `Status: ${transcription.status}\n` +
        `Price: ${transcription.price ? `$${Math.abs(parseFloat(transcription.price))}` : 'N/A'}`
      );
    } catch (err: any) {
      return this.errorResult(`Failed to fetch transcription: ${err.message}`);
    }
  }
}
