import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { wakeOnLanDefaultBroadcastAddress, wakeOnLanDefaultBroadcastPort, wakeOnLanDefaultName } from './wake_on_lan.constants.js';
import { normalizeWakeOnLanMac } from './wake_on_lan.packet.js';
import type { IWakeOnLanConfig, IWakeOnLanSnapshot, TWakeOnLanEntityKind, TWakeOnLanEntityMode } from './wake_on_lan.types.js';

export class WakeOnLanConfigFlow implements IConfigFlow<IWakeOnLanConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IWakeOnLanConfig>> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const mac = normalizeWakeOnLanMac(candidateArg.macAddress || this.stringValue(metadata.macAddress) || this.stringValue(metadata.mac));
    const host = candidateArg.host || this.stringValue(metadata.host);
    return {
      kind: 'form',
      title: 'Configure Wake on LAN',
      description: 'Provide the target MAC address and optional host/broadcast settings. Native UDP sends are attempted unless an injected executor handles the magic packet.',
      fields: [
        { name: 'macAddress', label: mac ? `MAC address (${mac})` : 'MAC address', type: 'text', required: true },
        { name: 'host', label: host ? `Host (${host})` : 'Host', type: 'text' },
        { name: 'name', label: candidateArg.name ? `Name (${candidateArg.name})` : 'Name', type: 'text' },
        { name: 'broadcastAddress', label: `Broadcast address (${this.stringValue(metadata.broadcastAddress) || wakeOnLanDefaultBroadcastAddress})`, type: 'text' },
        { name: 'broadcastPort', label: `Broadcast port (${this.numberValue(metadata.broadcastPort) || candidateArg.port || wakeOnLanDefaultBroadcastPort})`, type: 'number' },
        { name: 'entityMode', label: 'Entity type', type: 'select', options: [
          { label: 'Button', value: 'button' },
          { label: 'Switch', value: 'switch' },
          { label: 'Button and switch', value: 'both' },
        ] },
        { name: 'snapshotJson', label: 'Snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IWakeOnLanConfig>> {
    const metadata = candidateArg.metadata || {};
    const snapshot = this.snapshotFromInput(valuesArg.snapshotJson || metadata.snapshot);
    if (snapshot instanceof Error) {
      return { kind: 'error', title: 'Invalid Wake on LAN snapshot', error: snapshot.message };
    }

    const snapshotTarget = snapshot?.targets.find((targetArg) => normalizeWakeOnLanMac(targetArg.macAddress || targetArg.mac || targetArg.normalizedMac));
    const macAddress = normalizeWakeOnLanMac(this.stringValue(valuesArg.macAddress) || candidateArg.macAddress || this.stringValue(metadata.macAddress) || this.stringValue(metadata.mac) || snapshotTarget?.macAddress || snapshotTarget?.mac || snapshotTarget?.normalizedMac);
    if (!macAddress) {
      return { kind: 'error', title: 'Wake on LAN setup failed', error: 'Wake on LAN setup requires a valid MAC address.' };
    }

    const broadcastPort = this.numberValue(valuesArg.broadcastPort) ?? this.numberValue(metadata.broadcastPort) ?? candidateArg.port ?? snapshotTarget?.broadcastPort ?? wakeOnLanDefaultBroadcastPort;
    if (broadcastPort < 0 || broadcastPort > 65535) {
      return { kind: 'error', title: 'Wake on LAN setup failed', error: 'Broadcast port must be between 0 and 65535.' };
    }

    const entityMode = this.entityMode(valuesArg.entityMode) || this.entityMode(metadata.entityMode) || 'button';
    const config: IWakeOnLanConfig = {
      id: candidateArg.id || macAddress,
      macAddress,
      host: this.stringValue(valuesArg.host) || candidateArg.host || this.stringValue(metadata.host) || snapshotTarget?.host,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshotTarget?.name || `${wakeOnLanDefaultName} ${macAddress}`,
      broadcastAddress: this.stringValue(valuesArg.broadcastAddress) || this.stringValue(metadata.broadcastAddress) || snapshotTarget?.broadcastAddress || wakeOnLanDefaultBroadcastAddress,
      broadcastPort,
      entityTypes: this.entityTypes(entityMode),
      snapshot,
      metadata: {
        discoverySource: candidateArg.source,
        discoveryMetadata: metadata,
        nativeUdpImplemented: true,
      },
    };

    return {
      kind: 'done',
      title: 'Wake on LAN configured',
      config,
    };
  }

  private snapshotFromInput(valueArg: unknown): IWakeOnLanSnapshot | undefined | Error {
    if (valueArg && typeof valueArg === 'object') {
      return valueArg as IWakeOnLanSnapshot;
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(text) as IWakeOnLanSnapshot;
      if (!parsed || !Array.isArray(parsed.targets)) {
        return new Error('Snapshot JSON must include a targets array.');
      }
      return parsed;
    } catch (errorArg) {
      return errorArg instanceof Error ? errorArg : new Error(String(errorArg));
    }
  }

  private entityTypes(modeArg: TWakeOnLanEntityMode): TWakeOnLanEntityKind[] {
    return modeArg === 'both' ? ['button', 'switch'] : [modeArg];
  }

  private entityMode(valueArg: unknown): TWakeOnLanEntityMode | undefined {
    return valueArg === 'button' || valueArg === 'switch' || valueArg === 'both' ? valueArg : undefined;
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return Math.round(valueArg);
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) ? Math.round(parsed) : undefined;
    }
    return undefined;
  }
}
