import type {
  IFrontierSiliconCommandExecutorResult,
  IFrontierSiliconCommandRequest,
  IFrontierSiliconConfig,
  IFrontierSiliconDeviceInfo,
  IFrontierSiliconEqualiser,
  IFrontierSiliconMode,
  IFrontierSiliconPreset,
  IFrontierSiliconRawCommandRequest,
  IFrontierSiliconSnapshot,
  TFrontierSiliconFsapiOperation,
  TFrontierSiliconPlayState,
  TFrontierSiliconProtocol,
  TFrontierSiliconRepeatMode,
} from './frontier_silicon.types.js';
import { frontierSiliconDefaultPin, frontierSiliconDefaultPort, frontierSiliconDefaultTimeoutMs } from './frontier_silicon.types.js';

const fsapiNodes = {
  power: 'netRemote.sys.power',
  mode: 'netRemote.sys.mode',
  wiredMac: 'netRemote.sys.net.wired.macAddress',
  wiredActive: 'netRemote.sys.net.wired.interfaceEnable',
  wlanMac: 'netRemote.sys.net.wlan.macAddress',
  wlanActive: 'netRemote.sys.net.wlan.interfaceEnable',
  rssi: 'netRemote.sys.net.wlan.rssi',
  friendlyName: 'netRemote.sys.info.friendlyName',
  radioId: 'netRemote.sys.info.radioId',
  version: 'netRemote.sys.info.version',
  validModes: 'netRemote.sys.caps.validModes',
  equalisers: 'netRemote.sys.caps.eqPresets',
  eqPreset: 'netRemote.sys.audio.eqpreset',
  volumeSteps: 'netRemote.sys.caps.volumeSteps',
  volume: 'netRemote.sys.audio.volume',
  mute: 'netRemote.sys.audio.mute',
  status: 'netRemote.play.status',
  name: 'netRemote.play.info.name',
  text: 'netRemote.play.info.text',
  artist: 'netRemote.play.info.artist',
  album: 'netRemote.play.info.album',
  graphicUri: 'netRemote.play.info.graphicUri',
  duration: 'netRemote.play.info.duration',
  position: 'netRemote.play.position',
  control: 'netRemote.play.control',
  shuffle: 'netRemote.play.shuffle',
  repeat: 'netRemote.play.repeat',
  navState: 'netRemote.nav.state',
  navList: 'netRemote.nav.list',
  navigate: 'netRemote.nav.action.navigate',
  selectItem: 'netRemote.nav.action.selectItem',
  presets: 'netRemote.nav.presets',
  selectPreset: 'netRemote.nav.action.selectPreset',
} as const;

const playControl = {
  stop: 0,
  play: 1,
  pause: 2,
  next: 3,
  previous: 4,
} as const;

interface IFrontierSiliconFsapiDocument {
  status?: string;
  sessionId?: string;
  value?: string | number | boolean;
  items: IFrontierSiliconListItem[];
  listEnd?: boolean;
  raw: string;
}

interface IFrontierSiliconListItem {
  key: string;
  fields: Record<string, string | number | boolean | undefined>;
}

export class FrontierSiliconFsapiError extends Error {
  constructor(messageArg: string, public readonly status?: string) {
    super(messageArg);
    this.name = 'FrontierSiliconFsapiError';
  }
}

export class FrontierSiliconInvalidPinError extends FrontierSiliconFsapiError {
  constructor() {
    super('Frontier Silicon FSAPI access denied: invalid PIN.');
    this.name = 'FrontierSiliconInvalidPinError';
  }
}

export class FrontierSiliconNotImplementedError extends FrontierSiliconFsapiError {
  constructor(nodeArg: string) {
    super(`Frontier Silicon FSAPI node is not implemented: ${nodeArg}`, 'FS_NODE_DOES_NOT_EXIST');
    this.name = 'FrontierSiliconNotImplementedError';
  }
}

export class FrontierSiliconClient {
  private currentSnapshot?: IFrontierSiliconSnapshot;
  private restorePoint?: IFrontierSiliconSnapshot;
  private sessionId?: string;
  private cachedWebfsapiUrl?: string;

  constructor(private readonly config: IFrontierSiliconConfig) {
    this.currentSnapshot = config.snapshot ? this.normalizeSnapshot(this.cloneValue(config.snapshot), 'snapshot') : undefined;
  }

  public async getSnapshot(): Promise<IFrontierSiliconSnapshot> {
    if (this.currentSnapshot && this.config.snapshot) {
      return this.cloneValue(this.currentSnapshot);
    }
    if (!this.config.host && !this.config.webfsapiUrl && !this.config.commandExecutor) {
      return this.normalizeSnapshot(this.snapshotFromConfig(false, 'Frontier Silicon refresh requires config.host, config.webfsapiUrl, config.snapshot, or commandExecutor.'), 'runtime');
    }

    try {
      this.currentSnapshot = await this.fetchSnapshot();
      return this.cloneValue(this.currentSnapshot);
    } catch (errorArg) {
      return this.normalizeSnapshot(this.snapshotFromConfig(false, errorArg instanceof Error ? errorArg.message : String(errorArg)), 'runtime');
    }
  }

  public async validateConnection(): Promise<IFrontierSiliconSnapshot> {
    const snapshot = await this.fetchSnapshot();
    if (!snapshot.device.radioId && !snapshot.device.id) {
      throw new Error('Frontier Silicon device did not provide a stable identifier.');
    }
    return snapshot;
  }

  public async execute(requestArg: IFrontierSiliconCommandRequest): Promise<unknown> {
    if (requestArg.command === 'set_power') {
      if (typeof requestArg.powered !== 'boolean') {
        throw new Error('Frontier Silicon set_power requires powered.');
      }
      return this.setBoolean(fsapiNodes.power, requestArg.powered);
    }
    if (requestArg.command === 'play') {
      return this.setNumber(fsapiNodes.control, playControl.play);
    }
    if (requestArg.command === 'pause') {
      return this.setNumber(fsapiNodes.control, playControl.pause);
    }
    if (requestArg.command === 'stop') {
      return this.setNumber(fsapiNodes.control, playControl.stop);
    }
    if (requestArg.command === 'next_track') {
      return this.setNumber(fsapiNodes.control, playControl.next);
    }
    if (requestArg.command === 'previous_track') {
      return this.setNumber(fsapiNodes.control, playControl.previous);
    }
    if (requestArg.command === 'set_volume') {
      return this.setVolume(requestArg);
    }
    if (requestArg.command === 'volume_up') {
      return this.stepVolume(1);
    }
    if (requestArg.command === 'volume_down') {
      return this.stepVolume(-1);
    }
    if (requestArg.command === 'mute') {
      if (typeof requestArg.muted !== 'boolean') {
        throw new Error('Frontier Silicon mute requires muted.');
      }
      return this.setBoolean(fsapiNodes.mute, requestArg.muted);
    }
    if (requestArg.command === 'select_source') {
      return this.selectSource(this.requiredString(requestArg.source, 'Frontier Silicon select_source requires source.'));
    }
    if (requestArg.command === 'set_sound_mode') {
      return this.selectSoundMode(this.requiredString(requestArg.soundMode, 'Frontier Silicon set_sound_mode requires soundMode.'));
    }
    if (requestArg.command === 'set_repeat') {
      return this.setRepeat(this.requiredRepeat(requestArg.repeat));
    }
    if (requestArg.command === 'set_shuffle') {
      if (typeof requestArg.shuffle !== 'boolean') {
        throw new Error('Frontier Silicon set_shuffle requires shuffle.');
      }
      return this.setBoolean(fsapiNodes.shuffle, requestArg.shuffle);
    }
    if (requestArg.command === 'seek') {
      return this.setNumber(fsapiNodes.position, Math.max(0, Math.round(this.requiredNumber(requestArg.position, 'Frontier Silicon seek requires position.') * 1000)));
    }
    if (requestArg.command === 'play_preset') {
      return this.playPreset(requestArg);
    }
    if (requestArg.command === 'play_media') {
      return this.playMedia(requestArg);
    }
    if (requestArg.command === 'raw_command') {
      return this.rawCommand(requestArg);
    }
    throw new Error(`Unsupported Frontier Silicon command: ${requestArg.command}`);
  }

  public async snapshot(): Promise<IFrontierSiliconSnapshot> {
    this.restorePoint = await this.getSnapshot();
    return this.cloneValue(this.restorePoint);
  }

  public async restore(snapshotArg = this.restorePoint): Promise<void> {
    if (!snapshotArg) {
      throw new Error('Frontier Silicon restore requires a prior snapshot.');
    }
    if (typeof snapshotArg.player.power === 'boolean') {
      await this.execute({ command: 'set_power', powered: snapshotArg.player.power });
    }
    if (snapshotArg.player.mode?.label || snapshotArg.player.mode?.key) {
      await this.execute({ command: 'select_source', source: snapshotArg.player.mode.label || snapshotArg.player.mode.key });
    }
    if (typeof snapshotArg.player.volume === 'number') {
      await this.execute({ command: 'set_volume', volume: snapshotArg.player.volume });
    }
    if (typeof snapshotArg.player.muted === 'boolean') {
      await this.execute({ command: 'mute', muted: snapshotArg.player.muted });
    }
    if (snapshotArg.player.soundMode?.label) {
      await this.execute({ command: 'set_sound_mode', soundMode: snapshotArg.player.soundMode.label });
    }
    if (snapshotArg.player.repeat) {
      await this.execute({ command: 'set_repeat', repeat: snapshotArg.player.repeat });
    }
    if (typeof snapshotArg.player.shuffle === 'boolean') {
      await this.execute({ command: 'set_shuffle', shuffle: snapshotArg.player.shuffle });
    }
    if (snapshotArg.player.playState === 'playing') {
      await this.execute({ command: 'play' });
    } else if (snapshotArg.player.playState === 'paused') {
      await this.execute({ command: 'pause' });
    } else if (snapshotArg.player.playState === 'idle') {
      await this.execute({ command: 'stop' });
    }
  }

  public async destroy(): Promise<void> {}

  private async fetchSnapshot(): Promise<IFrontierSiliconSnapshot> {
    const [friendlyName, radioId, version, power, modes, equalisers, presets, macAddress, rssi] = await Promise.all([
      this.getString(fsapiNodes.friendlyName).catch(() => undefined),
      this.getString(fsapiNodes.radioId).catch(() => undefined),
      this.getString(fsapiNodes.version).catch(() => undefined),
      this.getBoolean(fsapiNodes.power),
      this.getModes().catch(() => []),
      this.getEqualisers().catch(() => []),
      this.getPresets().catch(() => []),
      this.getMacAddress().catch(() => undefined),
      this.getRssi().catch(() => undefined),
    ]);

    const [modeKey, volumeSteps] = await Promise.all([
      this.getNumber(fsapiNodes.mode).catch(() => undefined),
      this.getNumber(fsapiNodes.volumeSteps).catch(() => undefined),
    ]);
    const mode = modeKey === undefined ? undefined : modes.find((modeArg) => modeArg.key === String(modeKey)) || { key: String(modeKey), id: String(modeKey), label: String(modeKey) };
    const maxVolume = typeof volumeSteps === 'number' && volumeSteps > 0 ? volumeSteps - 1 : undefined;
    const player = power ? await this.getPoweredPlayerState(mode, maxVolume, equalisers) : {
      power: false,
      playState: 'off' as TFrontierSiliconPlayState,
      mode,
      source: mode?.label,
      maxVolume,
    };

    const endpoint = this.knownWebfsapiUrl();
    const endpointUrl = parseUrl(endpoint);
    const host = this.config.host || endpointUrl?.hostname;
    const port = this.config.port || urlPort(endpointUrl) || (host ? frontierSiliconDefaultPort : undefined);
    const name = friendlyName || this.config.name || host || 'Frontier Silicon';
    return this.normalizeSnapshot({
      device: {
        id: this.config.uniqueId || radioId || (host && port ? `${host}:${port}` : name),
        name,
        host,
        port,
        protocol: this.config.protocol || protocolFromUrl(endpointUrl) || 'http',
        deviceUrl: this.config.deviceUrl || (host ? this.deviceUrl(this.config.protocol || 'http', host, port || frontierSiliconDefaultPort) : undefined),
        webfsapiUrl: endpoint,
        manufacturer: this.config.manufacturer || 'Frontier Silicon',
        model: this.config.model || 'FSAPI Radio',
        version,
        radioId,
        macAddress,
        rssi,
      },
      player,
      modes,
      equalisers,
      presets,
      online: true,
      updatedAt: new Date().toISOString(),
      source: this.config.commandExecutor ? 'executor' : 'http',
    }, this.config.commandExecutor ? 'executor' : 'http');
  }

  private async getPoweredPlayerState(modeArg: IFrontierSiliconMode | undefined, maxVolumeArg: number | undefined, equalisersArg: IFrontierSiliconEqualiser[]): Promise<IFrontierSiliconSnapshot['player']> {
    const [status, volume, muted, mediaName, mediaText, mediaArtist, mediaAlbum, mediaImageUrl, durationMs, positionMs, repeatValue, shuffle, eqPreset] = await Promise.all([
      this.getNumber(fsapiNodes.status).catch(() => undefined),
      this.getNumber(fsapiNodes.volume).catch(() => undefined),
      this.getBoolean(fsapiNodes.mute).catch(() => undefined),
      this.getString(fsapiNodes.name).catch(() => undefined),
      this.getString(fsapiNodes.text).catch(() => undefined),
      this.getString(fsapiNodes.artist).catch(() => undefined),
      this.getString(fsapiNodes.album).catch(() => undefined),
      this.getString(fsapiNodes.graphicUri).catch(() => undefined),
      this.getNumber(fsapiNodes.duration).catch(() => undefined),
      this.getNumber(fsapiNodes.position).catch(() => undefined),
      this.getNumber(fsapiNodes.repeat).catch(() => undefined),
      this.getBoolean(fsapiNodes.shuffle).catch(() => undefined),
      this.getNumber(fsapiNodes.eqPreset).catch(() => undefined),
    ]);
    const soundMode = eqPreset === undefined ? undefined : equalisersArg.find((equaliserArg) => equaliserArg.key === String(eqPreset));
    return {
      power: true,
      playState: this.playState(status),
      playStatusCode: status,
      volume,
      maxVolume: maxVolumeArg,
      muted,
      mode: modeArg,
      source: modeArg?.label,
      repeat: this.repeatMode(repeatValue),
      shuffle,
      soundMode,
      mediaName,
      mediaText,
      mediaTitle: [mediaName, mediaText].filter(Boolean).join(' - ') || undefined,
      mediaArtist,
      mediaAlbum,
      mediaImageUrl,
      mediaDuration: this.msToSeconds(durationMs),
      mediaPosition: this.msToSeconds(positionMs),
    };
  }

  private async getModes(): Promise<IFrontierSiliconMode[]> {
    const items = await this.listItems(fsapiNodes.validModes);
    return items.map((itemArg) => ({
      key: itemArg.key,
      id: stringValue(itemArg.fields.id) || itemArg.key,
      label: stringValue(itemArg.fields.label) || stringValue(itemArg.fields.name) || stringValue(itemArg.fields.id) || itemArg.key,
      selectable: numberValue(itemArg.fields.selectable),
      streamable: numberValue(itemArg.fields.streamable),
      modetype: numberValue(itemArg.fields.modetype),
    }));
  }

  private async getEqualisers(): Promise<IFrontierSiliconEqualiser[]> {
    const items = await this.listItems(fsapiNodes.equalisers);
    return items.map((itemArg) => ({
      key: itemArg.key,
      label: stringValue(itemArg.fields.label) || stringValue(itemArg.fields.name) || itemArg.key,
    }));
  }

  private async getPresets(): Promise<IFrontierSiliconPreset[]> {
    const items = await this.listItems(fsapiNodes.presets);
    return items.map((itemArg) => ({
      key: numberValue(itemArg.key) ?? 0,
      type: stringValue(itemArg.fields.type),
      name: stringValue(itemArg.fields.name)?.trim(),
    })).filter((presetArg) => Boolean(presetArg.name));
  }

  private async getMacAddress(): Promise<string | undefined> {
    const wlanActive = await this.getBoolean(fsapiNodes.wlanActive).catch(() => undefined);
    if (wlanActive) {
      return this.getString(fsapiNodes.wlanMac);
    }
    return this.getString(fsapiNodes.wiredMac);
  }

  private async getRssi(): Promise<number | undefined> {
    const rssi = await this.getNumber(fsapiNodes.rssi);
    return typeof rssi === 'number' ? Math.round(rssi * 0.6 - 80) : undefined;
  }

  private async setVolume(requestArg: IFrontierSiliconCommandRequest): Promise<unknown> {
    const value = requestArg.volume ?? (typeof requestArg.volumeLevel === 'number' ? requestArg.volumeLevel * await this.maxVolume() : undefined);
    if (typeof value !== 'number' || !Number.isFinite(value)) {
      throw new Error('Frontier Silicon volume command requires volumeLevel or volume.');
    }
    return this.setNumber(fsapiNodes.volume, Math.max(0, Math.min(await this.maxVolume(), Math.round(value))));
  }

  private async stepVolume(directionArg: 1 | -1): Promise<unknown> {
    const snapshot = await this.getSnapshot();
    const current = snapshot.player.volume ?? await this.getNumber(fsapiNodes.volume) ?? 0;
    return this.setNumber(fsapiNodes.volume, Math.max(0, Math.min(await this.maxVolume(), current + directionArg)));
  }

  private async selectSource(sourceArg: string): Promise<unknown> {
    await this.setBoolean(fsapiNodes.power, true);
    const snapshot = await this.getSnapshot();
    const source = sourceArg.toLowerCase();
    const mode = snapshot.modes.find((modeArg) => modeArg.label.toLowerCase() === source || modeArg.id.toLowerCase() === source || modeArg.key === sourceArg);
    if (!mode) {
      throw new Error(`Unknown Frontier Silicon source: ${sourceArg}`);
    }
    return this.setRawValue(fsapiNodes.mode, mode.key);
  }

  private async selectSoundMode(soundModeArg: string): Promise<unknown> {
    const snapshot = await this.getSnapshot();
    const soundMode = soundModeArg.toLowerCase();
    const equaliser = snapshot.equalisers.find((equaliserArg) => equaliserArg.label.toLowerCase() === soundMode || equaliserArg.key === soundModeArg);
    if (!equaliser) {
      throw new Error(`Unknown Frontier Silicon sound mode: ${soundModeArg}`);
    }
    return this.setRawValue(fsapiNodes.eqPreset, equaliser.key);
  }

  private async setRepeat(repeatArg: TFrontierSiliconRepeatMode): Promise<unknown> {
    const value = repeatArg === 'one' ? 2 : repeatArg === 'all' ? 1 : 0;
    return this.setNumber(fsapiNodes.repeat, value);
  }

  private async playPreset(requestArg: IFrontierSiliconCommandRequest): Promise<unknown> {
    const presetIndex = requestArg.presetIndex ?? (typeof requestArg.preset === 'number' ? requestArg.preset - 1 : undefined);
    if (typeof presetIndex !== 'number' || !Number.isInteger(presetIndex) || presetIndex < 0) {
      throw new Error('Frontier Silicon play_preset requires a one-based preset number or zero-based presetIndex.');
    }
    await this.setBoolean(fsapiNodes.power, true);
    return this.setNumber(fsapiNodes.selectPreset, presetIndex);
  }

  private async playMedia(requestArg: IFrontierSiliconCommandRequest): Promise<unknown> {
    const mediaId = this.requiredString(requestArg.mediaId, 'Frontier Silicon play_media requires mediaId.');
    if (requestArg.mediaType && requestArg.mediaType !== 'channel' && requestArg.mediaType !== 'music') {
      throw new Error(`Frontier Silicon only supports channel media playback, got ${requestArg.mediaType}.`);
    }
    const [source, type, ...keys] = mediaId.split('/');
    if (source && source !== 'unknown') {
      await this.selectSource(source);
    }
    if (type === 'preset') {
      return this.playPreset({ command: 'play_preset', preset: Number(keys[0]) });
    }
    if (type === 'channels') {
      const numericKeys = keys.map((keyArg) => Number(keyArg));
      if (!numericKeys.length || numericKeys.some((keyArg) => !Number.isInteger(keyArg))) {
        throw new Error('Frontier Silicon channel mediaId must include a numeric navigation path.');
      }
      return this.selectNavigationItem(numericKeys);
    }
    throw new Error(`Unsupported Frontier Silicon mediaId: ${mediaId}`);
  }

  private async selectNavigationItem(pathArg: number[]): Promise<unknown> {
    await this.setNumber(fsapiNodes.navState, 1);
    for (const folder of pathArg.slice(0, -1)) {
      await this.setNumber(fsapiNodes.navigate, folder);
    }
    return this.setNumber(fsapiNodes.selectItem, pathArg[pathArg.length - 1]);
  }

  private async rawCommand(requestArg: IFrontierSiliconCommandRequest): Promise<unknown> {
    const path = requestArg.path || requestArg.node;
    if (!path) {
      throw new Error('Frontier Silicon raw command requires data.path or data.node.');
    }
    const operation = this.operationFromPath(path, requestArg.value);
    const node = this.nodeFromPath(path, operation);
    return this.callFsapi(operation, node, { value: requestArg.value, params: requestArg.params });
  }

  private operationFromPath(pathArg: string, valueArg: unknown): TFrontierSiliconFsapiOperation {
    const [operation] = pathArg.split('/');
    if (operation === 'GET' || operation === 'SET' || operation === 'LIST_GET_NEXT' || operation === 'CREATE_SESSION') {
      return operation;
    }
    return valueArg === undefined ? 'GET' : 'SET';
  }

  private nodeFromPath(pathArg: string, operationArg: TFrontierSiliconFsapiOperation): string | undefined {
    if (operationArg === 'CREATE_SESSION') {
      return undefined;
    }
    if (pathArg.startsWith(`${operationArg}/`)) {
      return pathArg.slice(operationArg.length + 1).split('/')[0];
    }
    return pathArg;
  }

  private async maxVolume(): Promise<number> {
    const snapshotMax = this.currentSnapshot?.player.maxVolume;
    if (typeof snapshotMax === 'number' && snapshotMax > 0) {
      return snapshotMax;
    }
    const steps = await this.getNumber(fsapiNodes.volumeSteps).catch(() => undefined);
    return typeof steps === 'number' && steps > 1 ? steps - 1 : 100;
  }

  private async setBoolean(nodeArg: string, valueArg: boolean): Promise<unknown> {
    return this.setNumber(nodeArg, valueArg ? 1 : 0);
  }

  private async setNumber(nodeArg: string, valueArg: number): Promise<unknown> {
    return this.setRawValue(nodeArg, valueArg);
  }

  private async setRawValue(nodeArg: string, valueArg: string | number | boolean): Promise<unknown> {
    return this.callFsapi('SET', nodeArg, { value: valueArg });
  }

  private async getString(nodeArg: string): Promise<string | undefined> {
    return stringValue((await this.callFsapi('GET', nodeArg)).value);
  }

  private async getNumber(nodeArg: string): Promise<number | undefined> {
    return numberValue((await this.callFsapi('GET', nodeArg)).value);
  }

  private async getBoolean(nodeArg: string): Promise<boolean | undefined> {
    const value = (await this.callFsapi('GET', nodeArg)).value;
    if (typeof value === 'boolean') {
      return value;
    }
    const number = numberValue(value);
    return typeof number === 'number' ? number !== 0 : undefined;
  }

  private async listItems(listNameArg: string): Promise<IFrontierSiliconListItem[]> {
    const result: IFrontierSiliconListItem[] = [];
    let start = -1;
    const count = 50;
    for (let index = 0; index < 20; index++) {
      const doc = await this.callFsapi('LIST_GET_NEXT', listNameArg, { listStart: start, params: { maxItems: count } });
      result.push(...doc.items);
      if (doc.listEnd || !doc.items.length) {
        break;
      }
      start += count;
    }
    return result;
  }

  private async callFsapi(operationArg: TFrontierSiliconFsapiOperation, nodeArg?: string, optionsArg: { value?: string | number | boolean; params?: Record<string, string | number | boolean>; listStart?: number; retrySession?: boolean } = {}): Promise<IFrontierSiliconFsapiDocument> {
    const path = this.fsapiPath(operationArg, nodeArg, optionsArg.listStart);
    const params: Record<string, string | number | boolean> = {
      pin: this.config.pin || frontierSiliconDefaultPin,
      ...(this.sessionId && operationArg !== 'CREATE_SESSION' ? { sid: this.sessionId } : {}),
      ...(optionsArg.value !== undefined ? { value: this.paramValue(optionsArg.value) } : {}),
      ...(optionsArg.params || {}),
    };
    const request = await this.rawRequest(operationArg, path, params, nodeArg, optionsArg.value);
    let doc: IFrontierSiliconFsapiDocument;
    if (this.config.commandExecutor) {
      doc = this.executorResultToDocument(path, await this.config.commandExecutor.execute(request));
    } else {
      doc = await this.requestHttp(path, request.url, optionsArg.retrySession !== false);
    }
    this.assertFsapiOk(doc, nodeArg || path);
    return doc;
  }

  private async requestHttp(pathArg: string, urlArg: string | undefined, retrySessionArg: boolean): Promise<IFrontierSiliconFsapiDocument> {
    if (!urlArg) {
      throw new Error('Frontier Silicon FSAPI command requires config.host, config.webfsapiUrl, or commandExecutor. Static snapshots are read-only.');
    }
    const controller = new AbortController();
    const timeout = globalThis.setTimeout(() => controller.abort(), this.config.timeoutMs || frontierSiliconDefaultTimeoutMs);
    try {
      const response = await globalThis.fetch(urlArg, { method: 'GET', signal: controller.signal });
      const text = await response.text();
      if (response.status === 403) {
        throw new FrontierSiliconInvalidPinError();
      }
      if (response.status === 404 && retrySessionArg) {
        await this.createSession();
        const retryRequest = await this.rawRequestFromUrl(pathArg, urlArg);
        return this.requestHttp(pathArg, retryRequest.url, false);
      }
      if (!response.ok) {
        throw new FrontierSiliconFsapiError(`Frontier Silicon FSAPI ${pathArg} failed with HTTP ${response.status}: ${text}`);
      }
      return parseFsapiXml(text);
    } catch (errorArg) {
      if (errorArg instanceof FrontierSiliconFsapiError) {
        throw errorArg;
      }
      if (retrySessionArg) {
        await this.createSession();
        const retryRequest = await this.rawRequestFromUrl(pathArg, urlArg);
        return this.requestHttp(pathArg, retryRequest.url, false);
      }
      throw errorArg;
    } finally {
      globalThis.clearTimeout(timeout);
    }
  }

  private async createSession(): Promise<void> {
    this.sessionId = undefined;
    const doc = await this.callFsapi('CREATE_SESSION', undefined, { retrySession: false });
    if (!doc.sessionId) {
      throw new FrontierSiliconFsapiError('Frontier Silicon FSAPI did not return a sessionId.');
    }
    this.sessionId = doc.sessionId;
  }

  private assertFsapiOk(docArg: IFrontierSiliconFsapiDocument, nodeArg: string): void {
    const status = docArg.status;
    if (!status || status === 'FS_OK' || status === 'FS_LIST_END') {
      return;
    }
    if (status === 'FS_NODE_DOES_NOT_EXIST') {
      throw new FrontierSiliconNotImplementedError(nodeArg);
    }
    if (status === 'FS_FAIL') {
      throw new FrontierSiliconFsapiError(`Frontier Silicon FSAPI command failed for ${nodeArg}.`, status);
    }
    if (status === 'FS_NODE_BLOCKED') {
      throw new FrontierSiliconFsapiError(`Frontier Silicon FSAPI node is blocked in the current mode: ${nodeArg}`, status);
    }
    if (status === 'FS_PACKET_BAD') {
      throw new FrontierSiliconFsapiError(`Frontier Silicon FSAPI command cannot be SET: ${nodeArg}`, status);
    }
    throw new FrontierSiliconFsapiError(`Unexpected Frontier Silicon FSAPI status ${status} for ${nodeArg}.`, status);
  }

  private fsapiPath(operationArg: TFrontierSiliconFsapiOperation, nodeArg: string | undefined, listStartArg: number | undefined): string {
    if (operationArg === 'CREATE_SESSION') {
      return 'CREATE_SESSION';
    }
    if (!nodeArg) {
      throw new Error(`Frontier Silicon ${operationArg} requires a node.`);
    }
    if (operationArg === 'LIST_GET_NEXT') {
      return `LIST_GET_NEXT/${nodeArg}/${listStartArg ?? -1}`;
    }
    return `${operationArg}/${nodeArg}`;
  }

  private async rawRequest(operationArg: TFrontierSiliconFsapiOperation, pathArg: string, paramsArg: Record<string, string | number | boolean>, nodeArg?: string, valueArg?: string | number | boolean): Promise<IFrontierSiliconRawCommandRequest> {
    const endpoint = await this.endpointForRequest();
    const endpointUrl = parseUrl(endpoint);
    const host = this.config.host || endpointUrl?.hostname;
    const port = this.config.port || urlPort(endpointUrl) || (host ? frontierSiliconDefaultPort : undefined);
    return {
      operation: operationArg,
      path: pathArg,
      node: nodeArg,
      value: valueArg,
      params: paramsArg,
      url: endpoint ? this.commandUrl(endpoint, pathArg, paramsArg) : undefined,
      method: 'GET',
      host,
      port,
      protocol: this.config.protocol || protocolFromUrl(endpointUrl),
      webfsapiUrl: endpoint,
      pin: this.config.pin || frontierSiliconDefaultPin,
      sessionId: this.sessionId,
    };
  }

  private async rawRequestFromUrl(pathArg: string, previousUrlArg: string | undefined): Promise<{ url?: string }> {
    if (!previousUrlArg) {
      return { url: undefined };
    }
    const url = new URL(previousUrlArg);
    if (this.sessionId) {
      url.searchParams.set('sid', this.sessionId);
    }
    url.searchParams.set('pin', this.config.pin || frontierSiliconDefaultPin);
    void pathArg;
    return { url: url.toString() };
  }

  private async endpointForRequest(): Promise<string | undefined> {
    const known = this.knownWebfsapiUrl();
    if (known) {
      return known;
    }
    if (this.config.commandExecutor) {
      return undefined;
    }
    return this.discoverWebfsapiEndpoint();
  }

  private knownWebfsapiUrl(): string | undefined {
    return trimTrailingSlash(this.config.webfsapiUrl || this.cachedWebfsapiUrl || this.currentSnapshot?.device.webfsapiUrl);
  }

  private async discoverWebfsapiEndpoint(): Promise<string | undefined> {
    const deviceUrl = this.config.deviceUrl || (this.config.host ? this.deviceUrl(this.config.protocol || 'http', this.config.host, this.config.port || frontierSiliconDefaultPort) : undefined);
    if (!deviceUrl) {
      return undefined;
    }
    const controller = new AbortController();
    const timeout = globalThis.setTimeout(() => controller.abort(), this.config.timeoutMs || frontierSiliconDefaultTimeoutMs);
    try {
      const response = await globalThis.fetch(deviceUrl, { method: 'GET', signal: controller.signal });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`Frontier Silicon device description failed with HTTP ${response.status}: ${text}`);
      }
      const endpoint = elementText(text, 'webfsapi');
      if (!endpoint) {
        throw new Error(`Frontier Silicon device description did not contain webfsapi: ${deviceUrl}`);
      }
      this.cachedWebfsapiUrl = trimTrailingSlash(endpoint);
      return this.cachedWebfsapiUrl;
    } finally {
      globalThis.clearTimeout(timeout);
    }
  }

  private commandUrl(endpointArg: string, pathArg: string, paramsArg: Record<string, string | number | boolean>): string {
    const url = new URL(`${trimTrailingSlash(endpointArg)}/${pathArg}`);
    for (const [key, value] of Object.entries(paramsArg)) {
      url.searchParams.set(key, String(value));
    }
    return url.toString();
  }

  private deviceUrl(protocolArg: TFrontierSiliconProtocol, hostArg: string, portArg: number): string {
    const includePort = !(protocolArg === 'http' && portArg === 80) && !(protocolArg === 'https' && portArg === 443);
    const host = hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;
    return `${protocolArg}://${host}${includePort ? `:${portArg}` : ''}/device`;
  }

  private executorResultToDocument(pathArg: string, resultArg: unknown): IFrontierSiliconFsapiDocument {
    if (this.isRecord(resultArg)) {
      const result = resultArg as IFrontierSiliconCommandExecutorResult;
      if (result.ok === false || result.success === false || result.status && result.status >= 400) {
        throw new FrontierSiliconFsapiError(`Frontier Silicon executor command ${pathArg} failed: ${JSON.stringify(resultArg)}`);
      }
      if (typeof result.response === 'string') {
        return parseFsapiXml(result.response);
      }
      if (typeof result.text === 'string') {
        return parseFsapiXml(result.text);
      }
      if (typeof result.data === 'string') {
        return parseFsapiXml(result.data);
      }
      if (this.isRecord(result.data)) {
        return this.documentFromRecord(result.data as Record<string, unknown>);
      }
      if ('status' in resultArg || 'value' in resultArg || 'items' in resultArg) {
        return this.documentFromRecord(resultArg as Record<string, unknown>);
      }
    }
    if (typeof resultArg === 'string') {
      return parseFsapiXml(resultArg);
    }
    if (resultArg === undefined || resultArg === true) {
      return { status: 'FS_OK', items: [], raw: '' };
    }
    throw new FrontierSiliconFsapiError(`Frontier Silicon executor command ${pathArg} returned no FSAPI response.`);
  }

  private documentFromRecord(recordArg: Record<string, unknown>): IFrontierSiliconFsapiDocument {
    const itemsValue = Array.isArray(recordArg.items) ? recordArg.items : [];
    return {
      status: stringValue(recordArg.status) || 'FS_OK',
      sessionId: stringValue(recordArg.sessionId),
      value: primitiveValue(recordArg.value),
      items: itemsValue.map((itemArg) => this.listItemFromRecord(itemArg)).filter((itemArg): itemArg is IFrontierSiliconListItem => Boolean(itemArg)),
      listEnd: Boolean(recordArg.listEnd),
      raw: JSON.stringify(recordArg),
    };
  }

  private listItemFromRecord(valueArg: unknown): IFrontierSiliconListItem | undefined {
    if (!this.isRecord(valueArg)) {
      return undefined;
    }
    const key = stringValue(valueArg.key);
    if (!key) {
      return undefined;
    }
    const fields = this.isRecord(valueArg.fields) ? valueArg.fields : valueArg;
    return { key, fields: Object.fromEntries(Object.entries(fields).map(([fieldKey, value]) => [fieldKey, primitiveValue(value)])) };
  }

  private normalizeSnapshot(snapshotArg: IFrontierSiliconSnapshot, sourceArg: IFrontierSiliconSnapshot['source']): IFrontierSiliconSnapshot {
    const endpointUrl = parseUrl(snapshotArg.device.webfsapiUrl || this.config.webfsapiUrl);
    const host = snapshotArg.device.host || this.config.host || endpointUrl?.hostname;
    const port = snapshotArg.device.port || this.config.port || urlPort(endpointUrl) || (host ? frontierSiliconDefaultPort : undefined);
    const name = snapshotArg.device.name || this.config.name || host || 'Frontier Silicon';
    return {
      ...snapshotArg,
      device: {
        ...snapshotArg.device,
        id: snapshotArg.device.id || this.config.uniqueId || snapshotArg.device.radioId || (host && port ? `${host}:${port}` : name),
        name,
        host,
        port,
        protocol: snapshotArg.device.protocol || this.config.protocol || protocolFromUrl(endpointUrl) || 'http',
        manufacturer: snapshotArg.device.manufacturer || this.config.manufacturer || 'Frontier Silicon',
        model: snapshotArg.device.model || this.config.model || 'FSAPI Radio',
      },
      player: {
        ...snapshotArg.player,
        playState: snapshotArg.online ? snapshotArg.player.playState || 'idle' : 'off',
      },
      modes: snapshotArg.modes || [],
      equalisers: snapshotArg.equalisers || [],
      presets: snapshotArg.presets || [],
      online: Boolean(snapshotArg.online),
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
    };
  }

  private snapshotFromConfig(onlineArg: boolean, errorArg?: string): IFrontierSiliconSnapshot {
    const endpointUrl = parseUrl(this.config.webfsapiUrl);
    const host = this.config.host || endpointUrl?.hostname;
    const port = this.config.port || urlPort(endpointUrl) || (host ? frontierSiliconDefaultPort : undefined);
    const name = this.config.name || host || 'Frontier Silicon';
    return {
      device: {
        id: this.config.uniqueId || (host && port ? `${host}:${port}` : name),
        name,
        host,
        port,
        protocol: this.config.protocol || protocolFromUrl(endpointUrl) || 'http',
        deviceUrl: this.config.deviceUrl,
        webfsapiUrl: this.config.webfsapiUrl,
        manufacturer: this.config.manufacturer || 'Frontier Silicon',
        model: this.config.model || 'FSAPI Radio',
      },
      player: { power: onlineArg, playState: onlineArg ? 'idle' : 'off' },
      modes: [],
      equalisers: [],
      presets: [],
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
    };
  }

  private playState(statusArg: number | undefined): TFrontierSiliconPlayState {
    if (statusArg === 1 || statusArg === 4) {
      return 'buffering';
    }
    if (statusArg === 2) {
      return 'playing';
    }
    if (statusArg === 3) {
      return 'paused';
    }
    return 'idle';
  }

  private repeatMode(valueArg: number | undefined): TFrontierSiliconRepeatMode | undefined {
    if (valueArg === undefined) {
      return undefined;
    }
    if (valueArg === 2) {
      return 'one';
    }
    if (valueArg === 1) {
      return 'all';
    }
    return 'off';
  }

  private msToSeconds(valueArg: number | undefined): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? Math.floor(valueArg / 1000) : undefined;
  }

  private paramValue(valueArg: string | number | boolean): string | number {
    return typeof valueArg === 'boolean' ? valueArg ? 1 : 0 : valueArg;
  }

  private requiredString(valueArg: unknown, errorArg: string): string {
    if (typeof valueArg !== 'string' || !valueArg) {
      throw new Error(errorArg);
    }
    return valueArg;
  }

  private requiredNumber(valueArg: unknown, errorArg: string): number {
    if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
      throw new Error(errorArg);
    }
    return valueArg;
  }

  private requiredRepeat(valueArg: unknown): TFrontierSiliconRepeatMode {
    if (valueArg === 'off' || valueArg === 'all' || valueArg === 'one') {
      return valueArg;
    }
    throw new Error('Frontier Silicon set_repeat requires repeat off, all, or one.');
  }

  private cloneValue<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }

  private isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));
  }
}

const parseFsapiXml = (xmlArg: string): IFrontierSiliconFsapiDocument => {
  const valueBody = elementInner(xmlArg, 'value');
  return {
    status: elementText(xmlArg, 'status'),
    sessionId: elementText(xmlArg, 'sessionId'),
    value: valueBody === undefined ? undefined : parseValueBody(valueBody),
    items: parseItems(xmlArg),
    listEnd: /<listend\b/i.test(xmlArg),
    raw: xmlArg,
  };
};

const parseItems = (xmlArg: string): IFrontierSiliconListItem[] => {
  const items: IFrontierSiliconListItem[] = [];
  const itemRegex = /<item\b([^>]*)>([\s\S]*?)<\/item>/gi;
  let itemMatch: RegExpExecArray | null;
  while ((itemMatch = itemRegex.exec(xmlArg))) {
    const key = attributeValue(itemMatch[1], 'key');
    if (!key) {
      continue;
    }
    const fields: Record<string, string | number | boolean | undefined> = {};
    const fieldRegex = /<field\b([^>]*)>([\s\S]*?)<\/field>/gi;
    let fieldMatch: RegExpExecArray | null;
    while ((fieldMatch = fieldRegex.exec(itemMatch[2]))) {
      const name = attributeValue(fieldMatch[1], 'name');
      if (name) {
        fields[name] = parseValueBody(fieldMatch[2]);
      }
    }
    items.push({ key, fields });
  }
  return items;
};

const parseValueBody = (bodyArg: string): string | number | boolean | undefined => {
  const text = elementText(bodyArg, 'c8_array');
  if (text !== undefined) {
    return text;
  }
  const number = elementText(bodyArg, 'u8') ?? elementText(bodyArg, 'u32') ?? elementText(bodyArg, 's32');
  if (number !== undefined) {
    const parsed = Number(number);
    return Number.isFinite(parsed) ? parsed : number;
  }
  const bool = elementText(bodyArg, 'bool');
  if (bool !== undefined) {
    return bool === '1' || bool.toLowerCase() === 'true';
  }
  return undefined;
};

const elementInner = (xmlArg: string, tagArg: string): string | undefined => {
  const match = new RegExp(`<${tagArg}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tagArg}>`, 'i').exec(xmlArg);
  return match?.[1];
};

const elementText = (xmlArg: string, tagArg: string): string | undefined => {
  const inner = elementInner(xmlArg, tagArg);
  return inner === undefined ? undefined : decodeXml(inner.trim());
};

const attributeValue = (attributesArg: string, nameArg: string): string | undefined => {
  const match = new RegExp(`${nameArg}\\s*=\\s*"([^"]*)"|${nameArg}\\s*=\\s*'([^']*)'`, 'i').exec(attributesArg);
  return match ? decodeXml(match[1] || match[2] || '') : undefined;
};

const decodeXml = (valueArg: string): string => valueArg
  .replace(/&amp;/g, '&')
  .replace(/&lt;/g, '<')
  .replace(/&gt;/g, '>')
  .replace(/&quot;/g, '"')
  .replace(/&apos;/g, "'");

const primitiveValue = (valueArg: unknown): string | number | boolean | undefined => {
  if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean') {
    return valueArg;
  }
  return undefined;
};

const stringValue = (valueArg: unknown): string | undefined => {
  if (typeof valueArg === 'string' && valueArg) {
    return valueArg;
  }
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return String(valueArg);
  }
  return undefined;
};

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

const parseUrl = (valueArg: string | undefined): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};

const urlPort = (urlArg: URL | undefined): number | undefined => urlArg?.port ? Number(urlArg.port) : undefined;
const protocolFromUrl = (urlArg: URL | undefined): TFrontierSiliconProtocol | undefined => urlArg?.protocol === 'https:' ? 'https' : urlArg?.protocol === 'http:' ? 'http' : undefined;
const trimTrailingSlash = (valueArg: string | undefined): string | undefined => valueArg?.replace(/\/+$/, '');
