<template>
  <teleport to="body">
    <transition name="slide-up">
      <div v-if="show" class="phone-modal" :class="{ 'phone-minimized': minimized }">
        <!-- Minimized bar -->
        <div v-if="minimized" class="phone-mini-bar" @click="minimized = false">
          <q-icon name="phone_in_talk" size="18px" color="green" />
          <span class="text-weight-medium q-ml-xs">{{ dialNumber || 'Telephone' }}</span>
          <q-space />
          <span class="text-caption text-green-7">{{ formattedDuration }}</span>
          <q-icon name="open_in_full" size="14px" color="grey-6" class="q-ml-sm" />
        </div>

        <!-- Full panel -->
        <div v-else class="phone-panel">
          <!-- Header -->
          <div class="phone-header">
            <q-icon name="phone" size="20px" color="indigo-6" class="q-mr-xs" />
            <span class="text-weight-bold" style="font-size:1rem">Telephone</span>
            <q-space />
            <q-badge v-if="deviceReady" color="green" class="q-mr-sm">
              {{ provider === 'sip' ? 'SIP' : 'Twilio' }}
            </q-badge>
            <q-badge v-else-if="deviceError" color="red" class="q-mr-sm">Erreur</q-badge>
            <q-badge v-else color="orange" class="q-mr-sm">
              <q-spinner-dots size="10px" class="q-mr-xs" />Init
            </q-badge>
            <q-btn flat dense round size="xs" icon="minimize" color="grey-6" @click="minimized = true" />
            <q-btn flat dense round size="xs" icon="close" color="grey-6" @click="close" />
          </div>

          <!-- Customer context -->
          <div v-if="customerName" class="phone-contact">
            <q-icon name="person" size="18px" color="indigo-5" />
            <span class="q-ml-xs text-weight-medium">{{ customerName }}</span>
          </div>

          <!-- Error banner -->
          <div v-if="deviceError" class="phone-error">
            <q-icon name="error_outline" size="16px" color="red" class="q-mr-xs" />
            <span class="text-caption text-red">{{ deviceError }}</span>
          </div>

          <!-- Dialer (before call) -->
          <div v-if="!inCall" class="phone-dialer">
            <q-input v-model="dialNumber" dense outlined placeholder="+1 514 555 1234"
              :input-style="{ fontSize: '1.1rem', textAlign: 'center', letterSpacing: '1px' }"
              class="q-mb-sm" @keydown.enter="startCall">
              <template #prepend><q-icon name="dialpad" color="grey-5" /></template>
              <template #append>
                <q-icon v-if="dialNumber" name="backspace" color="grey-5" class="cursor-pointer"
                  @click="dialNumber = dialNumber.slice(0, -1)" size="18px" />
              </template>
            </q-input>

            <!-- Dialpad -->
            <div class="dialpad q-mb-sm">
              <q-btn v-for="key in dialpadKeys" :key="key" flat dense class="dialpad-key"
                :label="key" @click="pressKey(key)" />
            </div>

            <div class="flex flex-center q-gutter-sm">
              <q-btn round color="green" icon="call" size="lg"
                :disable="!dialNumber.trim() || !deviceReady"
                :loading="connecting" @click="startCall">
                <q-tooltip>Appeler via WebRTC</q-tooltip>
              </q-btn>
            </div>
          </div>

          <!-- In-call controls -->
          <div v-if="inCall" class="phone-call-view">
            <div class="call-status-bar">
              <div class="call-info">
                <div class="text-weight-bold" style="font-size:1.1rem">{{ dialNumber }}</div>
                <div class="text-caption" :class="connected ? 'text-green' : 'text-orange'">
                  <q-spinner-dots v-if="!connected" size="12px" class="q-mr-xs" />
                  {{ connected ? formattedDuration : callStatusText }}
                </div>
              </div>
            </div>

            <!-- In-call actions -->
            <div class="call-controls">
              <div class="control-row">
                <div class="control-btn" @click="toggleMute">
                  <q-btn round flat :icon="muted ? 'mic_off' : 'mic'" size="md"
                    :color="muted ? 'red' : 'grey-7'" />
                  <span class="text-caption">{{ muted ? 'Unmute' : 'Mute' }}</span>
                </div>
                <div class="control-btn" @click="toggleDialpad">
                  <q-btn round flat icon="dialpad" size="md" color="grey-7" />
                  <span class="text-caption">Clavier</span>
                </div>
                <div class="control-btn" @click="toggleSpeaker">
                  <q-btn round flat :icon="speakerOn ? 'volume_up' : 'volume_off'" size="md"
                    :color="speakerOn ? 'indigo-6' : 'grey-7'" />
                  <span class="text-caption">HP</span>
                </div>
              </div>

              <!-- DTMF dialpad (toggled) -->
              <div v-if="showDtmf" class="dialpad q-my-sm">
                <q-btn v-for="key in dialpadKeys" :key="'dtmf-'+key" flat dense class="dialpad-key"
                  :label="key" @click="sendDtmf(key)" />
              </div>

              <!-- Hangup -->
              <div class="flex flex-center q-mt-sm">
                <q-btn round color="red" icon="call_end" size="lg" @click="endCall" />
              </div>
            </div>
          </div>

          <!-- Call ended — log bar -->
          <div v-if="callEnded" class="phone-log-bar">
            <q-icon name="check_circle" size="18px" color="green" class="q-mr-xs" />
            <span class="text-caption">Appel termine — {{ formattedDuration }}</span>
            <q-space />
            <q-btn dense unelevated size="sm" color="indigo-6" icon="save" label="Logger"
              @click="logCall" :loading="loggingCall" />
          </div>

          <!-- Footer -->
          <div class="phone-footer">
            <q-icon name="circle" size="8px" :color="deviceReady ? 'green' : 'grey-4'" class="q-mr-xs" />
            <span class="text-caption text-grey-6">
              {{ provider === 'sip' ? 'Fonoster SIP' : 'Twilio WebRTC' }} — {{ identity }}
            </span>
          </div>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup>
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
import { Notify } from 'quasar'
import { createDoc } from 'src/api/erp'
import { Device } from '@twilio/voice-sdk'
import { UserAgent, Registerer, Inviter, SessionState } from 'sip.js'

const HUB_URL = (window.location.hostname === 'localhost')
  ? 'http://localhost:3300'
  : 'https://msg.gigafibre.ca'

const SIP_WSS_URL = 'wss://voice.gigafibre.ca/ws' // Routr WSS endpoint

const props = defineProps({
  modelValue: { type: Boolean, default: false },
  initialNumber: { type: String, default: '' },
  customerName: { type: String, default: '' },
  customerErpName: { type: String, default: '' },
  provider: { type: String, default: 'twilio' }, // 'twilio' or 'sip'
})

const emit = defineEmits(['update:modelValue', 'call-ended'])

// UI state
const show = ref(props.modelValue)
const minimized = ref(false)
const dialNumber = ref(props.initialNumber || '')
const inCall = ref(false)
const connected = ref(false)
const connecting = ref(false)
const callEnded = ref(false)
const muted = ref(false)
const speakerOn = ref(true)
const showDtmf = ref(false)
const loggingCall = ref(false)
const callStatusText = ref('Connexion...')

// Twilio state
const deviceReady = ref(false)
const deviceError = ref('')
const identity = ref('')
const callStartTime = ref(null)
const callDurationSec = ref(0)
let durationTimer = null
let device = null       // Twilio Device
let activeCall = null   // Twilio Call or SIP Session
let sipUA = null        // SIP.js UserAgent
let sipRegisterer = null
let audioElement = null

const dialpadKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#']

watch(() => props.modelValue, (v) => { show.value = v })
watch(show, (v) => {
  emit('update:modelValue', v)
  if (v) initDevice()
})
watch(() => props.provider, () => {
  // Provider changed — re-init on next open
  cleanupDevices()
})
watch(() => props.initialNumber, (v) => { if (v) dialNumber.value = v })

const formattedDuration = computed(() => {
  const m = Math.floor(callDurationSec.value / 60)
  const s = callDurationSec.value % 60
  return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
})

// ── Initialize Device (Twilio or SIP) ──
async function initDevice () {
  if (props.provider === 'sip') {
    if (sipUA) return
    await initSipDevice()
  } else {
    if (device) return
    await initTwilioDevice()
  }
}

async function initTwilioDevice () {
  deviceError.value = ''
  try {
    const res = await fetch(HUB_URL + '/voice/token')
    if (!res.ok) throw new Error('Token fetch failed: ' + res.status)
    const data = await res.json()
    identity.value = data.identity

    device = new Device(data.token, {
      edge: 'ashburn',
      logLevel: 'warn',
      codecPreferences: ['opus', 'pcmu'],
    })

    device.on('registered', () => { deviceReady.value = true; deviceError.value = '' })
    device.on('error', (err) => { deviceError.value = err.message || 'Device error' })
    device.on('tokenWillExpire', async () => {
      try {
        const r = await fetch(HUB_URL + '/voice/token')
        const d = await r.json()
        device.updateToken(d.token)
      } catch {}
    })

    await device.register()
    deviceReady.value = true

    device.on('incoming', (call) => {
      show.value = true; minimized.value = false
      dialNumber.value = call.parameters.From || 'Inconnu'
      Notify.create({
        type: 'info', message: `Appel entrant de ${dialNumber.value}`,
        actions: [
          { label: 'Repondre', color: 'green', handler: () => answerIncoming(call) },
          { label: 'Refuser', color: 'red', handler: () => call.reject() },
        ],
        timeout: 30000,
      })
    })
  } catch (e) {
    deviceError.value = e.message
    console.error('[PhoneModal] Twilio init failed:', e)
  }
}

async function initSipDevice () {
  deviceError.value = ''
  try {
    // Get SIP credentials from Fonoster via targo-hub
    const res = await fetch(HUB_URL + '/voice/sip-config')
    if (!res.ok) throw new Error('SIP config fetch failed: ' + res.status)
    const cfg = await res.json()

    identity.value = cfg.extension || cfg.identity || 'sip-agent'
    const sipDomain = cfg.domain || 'voice.gigafibre.ca'
    const uri = UserAgent.makeURI(`sip:${cfg.extension}@${sipDomain}`)
    if (!uri) throw new Error('Invalid SIP URI')

    sipUA = new UserAgent({
      uri,
      transportOptions: { server: cfg.wssUrl || SIP_WSS_URL },
      authorizationUsername: cfg.authId || cfg.extension,
      authorizationPassword: cfg.authPassword,
      displayName: cfg.displayName || 'Targo Ops',
      logLevel: 'warn',
      delegate: {
        onInvite: (invitation) => {
          show.value = true; minimized.value = false
          const remote = invitation.remoteIdentity?.uri?.user || 'Inconnu'
          dialNumber.value = remote
          Notify.create({
            type: 'info', message: `Appel SIP entrant de ${remote}`,
            actions: [
              { label: 'Repondre', color: 'green', handler: () => answerSipIncoming(invitation) },
              { label: 'Refuser', color: 'red', handler: () => { try { invitation.reject() } catch {} } },
            ],
            timeout: 30000,
          })
        },
      },
    })

    await sipUA.start()
    sipRegisterer = new Registerer(sipUA)
    await sipRegisterer.register()
    deviceReady.value = true
  } catch (e) {
    deviceError.value = e.message
    console.error('[PhoneModal] SIP init failed:', e)
  }
}

function cleanupDevices () {
  deviceReady.value = false
  deviceError.value = ''
  if (device) { try { device.unregister(); device.destroy() } catch {} device = null }
  if (sipRegisterer) { try { sipRegisterer.unregister() } catch {} sipRegisterer = null }
  if (sipUA) { try { sipUA.stop() } catch {} sipUA = null }
  if (audioElement) { audioElement.srcObject = null }
}

function getAudioElement () {
  if (!audioElement) {
    audioElement = document.createElement('audio')
    audioElement.autoplay = true
    document.body.appendChild(audioElement)
  }
  return audioElement
}

function answerIncoming (call) {
  activeCall = call
  setupCallListeners(call)
  call.accept()
  inCall.value = true
  connected.value = true
  callEnded.value = false
  startDurationTimer()
}

function answerSipIncoming (invitation) {
  activeCall = invitation
  setupSipSessionListeners(invitation)
  invitation.accept({ sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } })
  inCall.value = true
  callEnded.value = false
}

function setupSipSessionListeners (session) {
  session.stateChange.addListener((state) => {
    if (state === SessionState.Established) {
      connected.value = true; connecting.value = false
      startDurationTimer()
      // Attach remote audio
      const pc = session.sessionDescriptionHandler?.peerConnection
      if (pc) {
        const audio = getAudioElement()
        pc.getReceivers().forEach((r) => {
          if (r.track?.kind === 'audio') audio.srcObject = new MediaStream([r.track])
        })
      }
    } else if (state === SessionState.Terminated) {
      stopDurationTimer()
      inCall.value = false; connected.value = false; connecting.value = false
      callEnded.value = true; muted.value = false; showDtmf.value = false
      activeCall = null
      if (audioElement) audioElement.srcObject = null
    }
  })
}

// ── Make Call ──
async function startCall () {
  if (!dialNumber.value.trim() || !deviceReady.value) return

  connecting.value = true
  callEnded.value = false
  callStatusText.value = 'Connexion...'

  // Normalize number
  let number = dialNumber.value.trim().replace(/[\s\-\(\)]/g, '')
  if (!number.startsWith('+')) {
    if (number.length === 10) number = '+1' + number
    else if (number.length === 11 && number.startsWith('1')) number = '+' + number
    else number = '+' + number
  }

  if (props.provider === 'sip') {
    await startSipCall(number)
  } else {
    await startTwilioCall(number)
  }
}

async function startTwilioCall (number) {
  try {
    activeCall = await device.connect({ params: { To: number } })
    setupCallListeners(activeCall)
    inCall.value = true
  } catch (e) {
    deviceError.value = 'Appel echoue: ' + e.message
    connecting.value = false
  }
}

async function startSipCall (number) {
  if (!sipUA) { deviceError.value = 'SIP not registered'; connecting.value = false; return }
  try {
    const domain = 'voice.gigafibre.ca'
    const target = UserAgent.makeURI(`sip:${number}@${domain}`)
    if (!target) throw new Error('Invalid SIP target')

    const inviter = new Inviter(sipUA, target, {
      sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } },
    })
    activeCall = inviter
    setupSipSessionListeners(inviter)
    await inviter.invite()
    inCall.value = true
  } catch (e) {
    deviceError.value = 'SIP call failed: ' + e.message
    connecting.value = false
  }
}

function setupCallListeners (call) {
  call.on('ringing', () => {
    callStatusText.value = 'Sonnerie...'
    connecting.value = false
  })

  call.on('accept', () => {
    connected.value = true
    connecting.value = false
    callStatusText.value = ''
    startDurationTimer()
  })

  call.on('disconnect', () => {
    stopDurationTimer()
    inCall.value = false
    connected.value = false
    connecting.value = false
    callEnded.value = true
    muted.value = false
    showDtmf.value = false
    activeCall = null
  })

  call.on('cancel', () => {
    inCall.value = false
    connected.value = false
    connecting.value = false
    callEnded.value = false
    activeCall = null
  })

  call.on('error', (err) => {
    deviceError.value = err.message || 'Call error'
    inCall.value = false
    connected.value = false
    connecting.value = false
    activeCall = null
  })
}

// ── Call Controls ──
function endCall () {
  if (!activeCall) return
  if (props.provider === 'sip') {
    try {
      if (activeCall.state === SessionState.Established) activeCall.bye()
      else activeCall.cancel?.() || activeCall.reject?.()
    } catch { /* session already terminated */ }
  } else {
    activeCall.disconnect()
  }
}

function toggleMute () {
  if (!activeCall) return
  muted.value = !muted.value
  if (props.provider === 'sip') {
    const pc = activeCall.sessionDescriptionHandler?.peerConnection
    if (pc) pc.getSenders().forEach(s => { if (s.track?.kind === 'audio') s.track.enabled = !muted.value })
  } else {
    activeCall.mute(muted.value)
  }
}

function toggleSpeaker () {
  speakerOn.value = !speakerOn.value
  // Browser doesn't support speaker toggle natively — this is a visual indicator
}

function toggleDialpad () {
  showDtmf.value = !showDtmf.value
}

function sendDtmf (digit) {
  if (!activeCall) return
  if (props.provider === 'sip') {
    try { activeCall.sessionDescriptionHandler?.sendDtmf(digit) } catch {}
  } else {
    activeCall.sendDigits(digit)
  }
}

function pressKey (key) {
  dialNumber.value += key
}

// ── Duration Timer ──
function startDurationTimer () {
  callStartTime.value = Date.now()
  callDurationSec.value = 0
  durationTimer = setInterval(() => {
    if (callStartTime.value) {
      callDurationSec.value = Math.floor((Date.now() - callStartTime.value) / 1000)
    }
  }, 1000)
}

function stopDurationTimer () {
  if (durationTimer) {
    clearInterval(durationTimer)
    durationTimer = null
  }
}

// ── Log Call ──
async function logCall () {
  loggingCall.value = true
  const phone = dialNumber.value.trim()
  const duration = callDurationSec.value
  const durationMin = Math.floor(duration / 60)
  const durationSec = duration % 60
  const durationStr = `${durationMin}m${durationSec.toString().padStart(2, '0')}s`

  try {
    await createDoc('Communication', {
      subject: 'Appel vers ' + phone,
      communication_type: 'Communication',
      communication_medium: 'Phone',
      sent_or_received: 'Sent',
      status: 'Linked',
      phone_no: phone,
      sender: 'sms@gigafibre.ca',
      sender_full_name: 'Targo Ops',
      content: `Appel vers ${phone} — Duree: ${durationStr}`,
      reference_doctype: 'Customer',
      reference_name: props.customerErpName || props.customerName,
    })
    Notify.create({ type: 'positive', message: 'Appel enregistre', timeout: 2000 })
    emit('call-ended', { direction: 'out', remote: phone, duration })
    callEnded.value = false
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  } finally {
    loggingCall.value = false
  }
}

// ── Close ──
function close () {
  if (inCall.value) {
    minimized.value = true
    return
  }
  stopDurationTimer()
  show.value = false
  inCall.value = false
  connected.value = false
  callEnded.value = false
  callStartTime.value = null
  callDurationSec.value = 0
}

// ── Lifecycle ──
onMounted(() => {
  if (show.value) initDevice()
})

onUnmounted(() => {
  stopDurationTimer()
  cleanupDevices()
})
</script>

<style scoped>
.phone-modal {
  position: fixed;
  bottom: 16px;
  right: 16px;
  z-index: 9998;
  width: 340px;
  background: white;
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.1);
  overflow: hidden;
  transition: all 0.3s ease;
}

.phone-minimized {
  width: 260px;
  border-radius: 24px;
}

.phone-mini-bar {
  display: flex;
  align-items: center;
  padding: 10px 16px;
  cursor: pointer;
}
.phone-mini-bar:hover { background: #f5f5f5; }

.phone-panel { display: flex; flex-direction: column; }

.phone-header {
  display: flex;
  align-items: center;
  padding: 10px 12px 8px;
  border-bottom: 1px solid #f0f0f0;
}

.phone-contact {
  display: flex;
  align-items: center;
  padding: 6px 16px;
  background: #f1f5f9;
  font-size: 0.88rem;
}

.phone-error {
  display: flex;
  align-items: center;
  padding: 6px 12px;
  background: #fef2f2;
}

.phone-dialer { padding: 12px 16px; }

.dialpad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  max-width: 200px;
  margin: 0 auto;
}
.dialpad-key {
  font-size: 1.1rem !important;
  font-weight: 600;
  min-height: 38px;
  border-radius: 50%;
}

.phone-call-view { display: flex; flex-direction: column; }

.call-status-bar {
  display: flex;
  align-items: center;
  padding: 16px;
  background: linear-gradient(135deg, #f0fdf0, #e8f5e9);
}
.call-info { flex: 1; text-align: center; }

.call-controls { padding: 12px 16px; }
.control-row {
  display: flex;
  justify-content: space-around;
  align-items: center;
}
.control-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  gap: 2px;
}
.control-btn .text-caption { font-size: 0.7rem; color: #666; }

.phone-log-bar {
  display: flex;
  align-items: center;
  padding: 8px 12px;
  background: #f0fdf0;
  border-top: 1px solid #e0e0e0;
}

.phone-footer {
  display: flex;
  align-items: center;
  padding: 5px 12px;
  border-top: 1px solid #f0f0f0;
  background: #fafafa;
}

.slide-up-enter-active, .slide-up-leave-active { transition: all 0.3s ease; }
.slide-up-enter-from, .slide-up-leave-to { transform: translateY(20px); opacity: 0; }
</style>
