import type {
  IBraviatvApp,
  IBraviatvChannel,
  IBraviatvConfig,
  IBraviatvPlayingInfo,
  IBraviatvSnapshot,
  IBraviatvSource,
  IBraviatvState,
  IBraviatvSystemInfo,
  IBraviatvVolumeInfo,
  TBraviatvRestService,
  TBraviatvSourceType,
} from './braviatv.types.js';

interface IBraviatvRestResponse {
  result?: unknown[];
  error?: [number, string] | unknown[];
  id?: number;
}

const defaultTimeoutMs = 10000;
const defaultAudioTarget = 'speaker';
const defaultRestVersion = '1.0';
const powerOnCode = 'AAAAAQAAAAEAAAAuAw==';

const fallbackCommands: Record<string, string> = {
  Power: 'AAAAAQAAAAEAAAAVAw==',
  PowerOn: powerOnCode,
  Input: 'AAAAAQAAAAEAAAAlAw==',
  SyncMenu: 'AAAAAgAAABoAAABYAw==',
  Hdmi1: 'AAAAAgAAABoAAABaAw==',
  Hdmi2: 'AAAAAgAAABoAAABbAw==',
  Hdmi3: 'AAAAAgAAABoAAABcAw==',
  Hdmi4: 'AAAAAgAAABoAAABdAw==',
  Num1: 'AAAAAQAAAAEAAAAAAw==',
  Num2: 'AAAAAQAAAAEAAAABAw==',
  Num3: 'AAAAAQAAAAEAAAACAw==',
  Num4: 'AAAAAQAAAAEAAAADAw==',
  Num5: 'AAAAAQAAAAEAAAAEAw==',
  Num6: 'AAAAAQAAAAEAAAAFAw==',
  Num7: 'AAAAAQAAAAEAAAAGAw==',
  Num8: 'AAAAAQAAAAEAAAAHAw==',
  Num9: 'AAAAAQAAAAEAAAAIAw==',
  Num0: 'AAAAAQAAAAEAAAAJAw==',
  Dot: 'AAAAAgAAAJcAAAAdAw==',
  CC: 'AAAAAgAAAJcAAAAoAw==',
  Red: 'AAAAAgAAAJcAAAAlAw==',
  Green: 'AAAAAgAAAJcAAAAmAw==',
  Yellow: 'AAAAAgAAAJcAAAAnAw==',
  Blue: 'AAAAAgAAAJcAAAAkAw==',
  Up: 'AAAAAQAAAAEAAAB0Aw==',
  Down: 'AAAAAQAAAAEAAAB1Aw==',
  Right: 'AAAAAQAAAAEAAAAzAw==',
  Left: 'AAAAAQAAAAEAAAA0Aw==',
  Confirm: 'AAAAAQAAAAEAAABlAw==',
  Help: 'AAAAAgAAAMQAAABNAw==',
  Display: 'AAAAAQAAAAEAAAA6Aw==',
  Options: 'AAAAAgAAAJcAAAA2Aw==',
  Back: 'AAAAAgAAAJcAAAAjAw==',
  Home: 'AAAAAQAAAAEAAABgAw==',
  VolumeUp: 'AAAAAQAAAAEAAAASAw==',
  VolumeDown: 'AAAAAQAAAAEAAAATAw==',
  Mute: 'AAAAAQAAAAEAAAAUAw==',
  Audio: 'AAAAAQAAAAEAAAAXAw==',
  ChannelUp: 'AAAAAQAAAAEAAAAQAw==',
  ChannelDown: 'AAAAAQAAAAEAAAARAw==',
  Play: 'AAAAAgAAAJcAAAAaAw==',
  Pause: 'AAAAAgAAAJcAAAAZAw==',
  Stop: 'AAAAAgAAAJcAAAAYAw==',
  FlashPlus: 'AAAAAgAAAJcAAAB4Aw==',
  FlashMinus: 'AAAAAgAAAJcAAAB5Aw==',
  Prev: 'AAAAAgAAAJcAAAA8Aw==',
  Next: 'AAAAAgAAAJcAAAA9Aw==',
};

export class BraviatvAuthError extends Error {
  constructor(messageArg: string) {
    super(messageArg);
    this.name = 'BraviatvAuthError';
  }
}

export class BraviatvLiveControlError extends Error {
  constructor(messageArg: string) {
    super(messageArg);
    this.name = 'BraviatvLiveControlError';
  }
}

export class BraviatvClient {
  private commandCache?: Record<string, string>;
  private irccEndpoint: string;

  constructor(private readonly config: IBraviatvConfig) {
    this.irccEndpoint = config.irccEndpoint || 'ircc';
    this.commandCache = config.commands ? { ...fallbackCommands, ...config.commands } : undefined;
  }

  public async getSnapshot(): Promise<IBraviatvSnapshot> {
    if (this.config.snapshot) {
      return this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot));
    }

    if (this.hasLivePskConfig()) {
      try {
        return await this.getLiveSnapshot();
      } catch (errorArg) {
        if (errorArg instanceof BraviatvAuthError) {
          throw errorArg;
        }
        return this.normalizeSnapshot(this.snapshotFromManualConfig(this.errorMessage(errorArg)));
      }
    }

    return this.normalizeSnapshot(this.snapshotFromManualConfig());
  }

  public async turnOn(): Promise<void> {
    this.assertLivePskSupported('turn_on');
    try {
      await this.sendRestQuick('system', 'setPowerStatus', { status: true });
    } catch {
      await this.sendIrcc(powerOnCode);
    }
    this.updateLocalState({ powerStatus: 'active', power: 'on', available: true });
  }

  public async turnOff(): Promise<void> {
    await this.sendRestQuick('system', 'setPowerStatus', { status: false });
    this.updateLocalState({ powerStatus: 'standby', power: 'off', playback: 'off' });
  }

  public async setVolumeLevel(volumeLevelArg: number): Promise<void> {
    const volume = Math.max(0, Math.min(100, Math.round(volumeLevelArg * 100)));
    await this.sendRestQuick('audio', 'setAudioVolume', { target: defaultAudioTarget, volume: String(volume) });
    this.updateLocalState({ volumeLevel: volume / 100, volumePercent: volume });
  }

  public async volumeUp(): Promise<void> {
    await this.stepVolume(1);
  }

  public async volumeDown(): Promise<void> {
    await this.stepVolume(-1);
  }

  public async muteVolume(mutedArg: boolean): Promise<void> {
    await this.sendRestQuick('audio', 'setAudioMute', { status: mutedArg });
    this.updateLocalState({ muted: mutedArg });
  }

  public async mediaPlay(): Promise<void> {
    await this.sendCommand('Play');
    this.updateLocalState({ playback: 'playing' });
  }

  public async mediaPause(): Promise<void> {
    await this.sendCommand('Pause');
    this.updateLocalState({ playback: 'paused' });
  }

  public async mediaStop(): Promise<void> {
    await this.sendCommand('Stop');
    this.updateLocalState({ playback: 'idle' });
  }

  public async nextTrack(): Promise<void> {
    await this.sendCommand('Next');
  }

  public async previousTrack(): Promise<void> {
    await this.sendCommand('Prev');
  }

  public async selectSource(sourceArg: string): Promise<void> {
    const source = sourceArg.trim();
    if (!source) {
      throw new Error('Sony Bravia TV select_source requires a source name or URI.');
    }

    const item = await this.findSource(source);
    await this.startSource(item.uri, item.type);
    this.updateLocalState({ powerStatus: 'active', power: 'on', source: item.title, sourceUri: item.uri });
  }

  public async playMedia(mediaTypeArg: string, mediaIdArg: string): Promise<void> {
    const normalizedType = mediaTypeArg.toLowerCase();
    if (normalizedType !== 'app' && normalizedType !== 'channel') {
      throw new Error(`Sony Bravia TV play_media supports app or channel media only, not ${mediaTypeArg}.`);
    }
    const source = await this.findSource(mediaIdArg, normalizedType as TBraviatvSourceType);
    await this.startSource(source.uri, source.type);
  }

  public async sendCommand(commandArg: string): Promise<void> {
    const code = await this.commandCode(commandArg);
    if (!code) {
      throw new Error(`Unsupported Sony Bravia TV IRCC command: ${commandArg}`);
    }
    await this.sendIrcc(code);
  }

  public async sendCommands(commandsArg: string[], repeatsArg = 1): Promise<void> {
    const repeats = Math.max(1, Math.floor(repeatsArg));
    for (let repeat = 0; repeat < repeats; repeat += 1) {
      for (const command of commandsArg) {
        await this.sendCommand(command);
      }
    }
  }

  public async reboot(): Promise<void> {
    await this.sendRestQuick('system', 'requestReboot');
  }

  public async terminateApps(): Promise<void> {
    await this.sendRestQuick('appControl', 'terminateApps');
  }

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

  private async getLiveSnapshot(): Promise<IBraviatvSnapshot> {
    const powerStatus = await this.getPowerStatus();
    const systemInfo = await this.getSystemInfo().catch(() => this.systemInfoFromConfig());
    const state: IBraviatvState = {
      ...this.config.state,
      powerStatus,
      power: this.powerFromStatus(powerStatus),
      available: true,
    };

    let sources = this.config.sources || [];
    let apps = this.config.apps || [];
    let channels = this.config.channels || [];
    let commands = this.config.commands;

    if (state.power === 'on') {
      const [volumeInfo, playingInfo, liveSources, liveApps, liveChannels, liveCommands] = await Promise.all([
        this.getVolumeInfo().catch(() => undefined),
        this.getPlayingInfo().catch(() => undefined),
        this.getExternalInputsStatus().catch(() => sources),
        this.getAppList().catch(() => apps),
        this.config.fetchChannels ? this.getContentListAll('tv').catch(() => channels) : Promise.resolve(channels),
        this.getCommandList().catch(() => commands),
      ]);
      sources = liveSources;
      apps = liveApps;
      channels = liveChannels;
      commands = liveCommands;
      this.applyVolumeInfo(state, volumeInfo);
      this.applyPlayingInfo(state, playingInfo);
    }

    return this.normalizeSnapshot({
      systemInfo,
      state,
      sources,
      apps,
      channels,
      commands,
      updatedAt: new Date().toISOString(),
    });
  }

  private async getPowerStatus(): Promise<string> {
    const response = await this.sendRest('system', 'getPowerStatus', undefined, defaultRestVersion, 5000);
    return this.firstResult<{ status?: string }>(response)?.status || 'unknown';
  }

  private async getSystemInfo(): Promise<IBraviatvSystemInfo> {
    const response = await this.sendRest('system', 'getSystemInformation');
    const info = this.firstResult<IBraviatvSystemInfo>(response) || {};
    this.config.macAddress = info.macAddr || this.config.macAddress;
    this.config.cid = info.cid || this.config.cid;
    return info;
  }

  private async getVolumeInfo(targetArg = defaultAudioTarget): Promise<IBraviatvVolumeInfo | undefined> {
    const response = await this.sendRest('audio', 'getVolumeInformation');
    const outputs = this.firstResult<IBraviatvVolumeInfo[]>(response) || [];
    return outputs.find((outputArg) => outputArg.target === targetArg) || outputs[0];
  }

  private async getPlayingInfo(): Promise<IBraviatvPlayingInfo | undefined> {
    const response = await this.sendRest('avContent', 'getPlayingContentInfo');
    return this.firstResult<IBraviatvPlayingInfo>(response);
  }

  private async getExternalInputsStatus(): Promise<IBraviatvSource[]> {
    const response = await this.sendRest('avContent', 'getCurrentExternalInputsStatus');
    return (this.firstResult<IBraviatvSource[]>(response) || []).map((sourceArg) => ({ ...sourceArg, type: 'input' }));
  }

  private async getAppList(): Promise<IBraviatvApp[]> {
    const response = await this.sendRest('appControl', 'getApplicationList');
    return (this.firstResult<IBraviatvApp[]>(response) || []).map((appArg) => ({ ...appArg, type: 'app' }));
  }

  private async getSourceList(schemeArg: string): Promise<string[]> {
    const response = await this.sendRest('avContent', 'getSourceList', { scheme: schemeArg });
    return (this.firstResult<Array<{ source?: string }>>(response) || []).map((itemArg) => itemArg.source).filter((itemArg): itemArg is string => Boolean(itemArg));
  }

  private async getContentCount(sourceArg: string): Promise<number> {
    const response = await this.sendRest('avContent', 'getContentCount', { source: sourceArg }, defaultRestVersion, 20000);
    return this.firstResult<{ count?: number }>(response)?.count || 0;
  }

  private async getContentList(sourceArg: string, indexArg: number, countArg: number): Promise<IBraviatvChannel[]> {
    const response = await this.sendRest('avContent', 'getContentList', { source: sourceArg, stIdx: indexArg, cnt: countArg }, defaultRestVersion, 20000);
    return (this.firstResult<IBraviatvChannel[]>(response) || []).map((channelArg) => ({ ...channelArg, type: 'channel' }));
  }

  private async getContentListAll(schemeArg: string): Promise<IBraviatvChannel[]> {
    const channels: IBraviatvChannel[] = [];
    for (const source of await this.getSourceList(schemeArg)) {
      const total = await this.getContentCount(source);
      for (let index = 0; index < total; index += 50) {
        channels.push(...await this.getContentList(source, index, Math.min(50, total - index)));
      }
    }
    return channels;
  }

  private async getCommandList(): Promise<Record<string, string>> {
    if (this.commandCache) {
      return this.commandCache;
    }
    const response = await this.sendRest('system', 'getRemoteControllerInfo');
    const remoteCommands = Array.isArray(response.result?.[1]) ? response.result[1] as Array<{ name?: string; value?: string }> : [];
    this.commandCache = {
      ...fallbackCommands,
      ...Object.fromEntries(remoteCommands.filter((itemArg) => itemArg.name && itemArg.value).map((itemArg) => [itemArg.name as string, itemArg.value as string])),
    };
    return this.commandCache;
  }

  private async stepVolume(stepArg: number): Promise<void> {
    const prefix = stepArg > 0 ? '+' : '';
    await this.sendRestQuick('audio', 'setAudioVolume', { target: defaultAudioTarget, volume: `${prefix}${stepArg}` });
  }

  private async findSource(queryArg: string, sourceTypeArg?: TBraviatvSourceType): Promise<Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>>> {
    const direct = this.directSource(queryArg, sourceTypeArg);
    if (direct) {
      return direct;
    }

    const snapshot = await this.getSnapshot();
    const items = [...snapshot.sources, ...snapshot.apps, ...(snapshot.channels || [])].map((itemArg) => ({
      ...itemArg,
      type: itemArg.type || this.inferSourceType(itemArg.uri),
    }));
    const exact = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && (itemArg.uri === queryArg || itemArg.title.toLowerCase() === queryArg.toLowerCase()));
    if (exact) {
      return { title: exact.title, uri: exact.uri, type: exact.type };
    }

    const coarse = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && itemArg.title.toLowerCase().includes(queryArg.toLowerCase()));
    if (coarse) {
      return { title: coarse.title, uri: coarse.uri, type: coarse.type };
    }

    throw new Error(`Sony Bravia TV source is not known: ${queryArg}`);
  }

  private directSource(sourceArg: string, sourceTypeArg?: TBraviatvSourceType): Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>> | undefined {
    if (sourceArg.startsWith('extInput:') || sourceArg.startsWith('tv:')) {
      return { title: sourceArg, uri: sourceArg, type: sourceArg.startsWith('tv:') ? 'channel' : 'input' };
    }
    if (sourceArg.startsWith('com.sony.dtv.') || sourceArg.startsWith('localapp:')) {
      return { title: sourceArg, uri: sourceArg, type: sourceTypeArg || 'app' };
    }
    return undefined;
  }

  private async startSource(uriArg: string, sourceTypeArg: TBraviatvSourceType): Promise<void> {
    if (sourceTypeArg === 'app') {
      await this.sendRestQuick('appControl', 'setActiveApp', { uri: uriArg });
      return;
    }
    await this.sendRestQuick('avContent', 'setPlayContent', { uri: uriArg });
  }

  private async sendRestQuick(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion): Promise<boolean> {
    const response = await this.sendRest(serviceArg, methodArg, paramsArg, versionArg);
    return Boolean(response.result);
  }

  private async sendRest(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion, timeoutMsArg?: number): Promise<IBraviatvRestResponse> {
    this.assertLivePskSupported(`${serviceArg}.${methodArg}`);
    const params = paramsArg === undefined ? [] : Array.isArray(paramsArg) ? paramsArg : [paramsArg];
    const response = await this.postJson(this.serviceUrl(serviceArg), {
      method: methodArg,
      params,
      id: 1,
      version: versionArg,
    }, timeoutMsArg);

    if (response.error) {
      const [code, message] = response.error;
      const text = String(message || code || 'Unknown REST API error');
      if (Number(code) === 401) {
        throw new BraviatvAuthError(`Sony Bravia TV authentication failed for ${methodArg}: ${text}`);
      }
      if (text.includes('not power-on')) {
        throw new BraviatvLiveControlError(`Sony Bravia TV is turned off and rejected ${methodArg}.`);
      }
      throw new BraviatvLiveControlError(`Sony Bravia TV REST ${methodArg} failed: ${text}`);
    }
    return response;
  }

  private async sendIrcc(codeArg: string): Promise<boolean> {
    this.assertLivePskSupported('IRCC command');
    try {
      return await this.postIrcc(this.irccEndpoint, codeArg);
    } catch (errorArg) {
      if (this.errorMessage(errorArg).includes('HTTP 404')) {
        this.irccEndpoint = this.irccEndpoint === 'ircc' ? 'IRCC' : 'ircc';
        return this.postIrcc(this.irccEndpoint, codeArg);
      }
      throw errorArg;
    }
  }

  private async postIrcc(endpointArg: string, codeArg: string): Promise<boolean> {
    const response = await this.postText(this.serviceUrl(endpointArg), this.irccBody(codeArg), {
      SOAPACTION: '"urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"',
      'Content-Type': 'text/xml; charset=UTF-8',
    });
    return response;
  }

  private async postJson(urlArg: string, bodyArg: unknown, timeoutMsArg?: number): Promise<IBraviatvRestResponse> {
    const text = await this.request(urlArg, {
      'Content-Type': 'application/json; charset=UTF-8',
    }, JSON.stringify(bodyArg), timeoutMsArg);
    try {
      return (text ? JSON.parse(text) : {}) as IBraviatvRestResponse;
    } catch (errorArg) {
      throw new BraviatvLiveControlError(`Sony Bravia TV returned invalid JSON: ${this.errorMessage(errorArg)}`);
    }
  }

  private async postText(urlArg: string, bodyArg: string, headersArg: Record<string, string>): Promise<boolean> {
    await this.request(urlArg, headersArg, bodyArg);
    return true;
  }

  private async request(urlArg: string, headersArg: Record<string, string>, bodyArg: string, timeoutMsArg?: number): Promise<string> {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMsArg || this.config.timeoutMs || defaultTimeoutMs);
    try {
      const response = await globalThis.fetch(urlArg, {
        method: 'POST',
        headers: {
          ...headersArg,
          'Cache-Control': 'no-cache',
          Connection: 'keep-alive',
          'X-Auth-PSK': this.config.psk as string,
        },
        body: bodyArg,
        signal: controller.signal,
      });
      const text = await response.text();
      if (response.status === 401 || response.status === 403) {
        throw new BraviatvAuthError(`Sony Bravia TV authentication failed with HTTP ${response.status}.`);
      }
      if (!response.ok) {
        throw new BraviatvLiveControlError(`Sony Bravia TV request failed with HTTP ${response.status}: ${text}`);
      }
      return text;
    } catch (errorArg) {
      if (errorArg instanceof BraviatvAuthError || errorArg instanceof BraviatvLiveControlError) {
        throw errorArg;
      }
      if (errorArg instanceof Error && errorArg.name === 'AbortError') {
        throw new BraviatvLiveControlError(`Sony Bravia TV request timed out after ${timeoutMsArg || this.config.timeoutMs || defaultTimeoutMs}ms.`);
      }
      throw new BraviatvLiveControlError(`Sony Bravia TV request failed: ${this.errorMessage(errorArg)}`);
    } finally {
      clearTimeout(timeout);
    }
  }

  private assertLivePskSupported(commandArg: string): void {
    if (!this.config.host) {
      throw new BraviatvLiveControlError(`Sony Bravia TV host is required for live ${commandArg}.`);
    }
    if (!this.config.psk) {
      if (this.config.pin || this.config.usePsk === false) {
        throw new BraviatvLiveControlError(`Sony Bravia TV PIN pairing/cookie authentication is not implemented in this native TypeScript port. Configure a PSK to use live ${commandArg}.`);
      }
      throw new BraviatvLiveControlError(`Sony Bravia TV PSK is required for live ${commandArg}.`);
    }
  }

  private hasLivePskConfig(): boolean {
    return Boolean(this.config.host && this.config.psk);
  }

  private serviceUrl(serviceArg: string): string {
    const protocol = this.config.useSsl ? 'https' : 'http';
    const port = this.config.port ? `:${this.config.port}` : '';
    return `${protocol}://${this.config.host}${port}/sony/${serviceArg}`;
  }

  private irccBody(codeArg: string): string {
    return `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1"><IRCCCode>${codeArg}</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>`;
  }

  private firstResult<TResult>(responseArg: IBraviatvRestResponse): TResult | undefined {
    return responseArg.result?.[0] as TResult | undefined;
  }

  private async commandCode(commandArg: string): Promise<string | undefined> {
    const command = commandArg.trim();
    if (this.looksLikeIrccCode(command)) {
      return command;
    }

    const commands = await this.getCommandList().catch(() => ({ ...fallbackCommands, ...(this.config.commands || {}) }));
    const candidates = [command, this.normalizeCommand(command)].filter(Boolean);
    for (const candidate of candidates) {
      if (commands[candidate]) {
        return commands[candidate];
      }
      const lowerCandidate = candidate.toLowerCase();
      const entry = Object.entries(commands).find(([name]) => name.toLowerCase() === lowerCandidate);
      if (entry) {
        return entry[1];
      }
    }
    return undefined;
  }

  private normalizeCommand(commandArg: string): string {
    const normalized = commandArg.replace(/[_\s-]+/g, '').toLowerCase();
    const aliases: Record<string, string> = {
      power: 'Power',
      poweron: 'PowerOn',
      input: 'Input',
      source: 'Input',
      hdmi1: 'Hdmi1',
      hdmi2: 'Hdmi2',
      hdmi3: 'Hdmi3',
      hdmi4: 'Hdmi4',
      up: 'Up',
      down: 'Down',
      left: 'Left',
      right: 'Right',
      ok: 'Confirm',
      enter: 'Confirm',
      select: 'Confirm',
      confirm: 'Confirm',
      back: 'Back',
      return: 'Back',
      home: 'Home',
      menu: 'Home',
      options: 'Options',
      info: 'Display',
      display: 'Display',
      volumeup: 'VolumeUp',
      volup: 'VolumeUp',
      volumedown: 'VolumeDown',
      voldown: 'VolumeDown',
      mute: 'Mute',
      channelup: 'ChannelUp',
      chup: 'ChannelUp',
      channeldown: 'ChannelDown',
      chdown: 'ChannelDown',
      play: 'Play',
      pause: 'Pause',
      stop: 'Stop',
      next: 'Next',
      previous: 'Prev',
      prev: 'Prev',
    };
    return aliases[normalized] || commandArg;
  }

  private looksLikeIrccCode(valueArg: string): boolean {
    return /^[A-Za-z0-9+/]+={0,2}$/.test(valueArg) && valueArg.length >= 16 && valueArg.includes('Aw');
  }

  private snapshotFromManualConfig(errorMessageArg?: string): IBraviatvSnapshot {
    const systemInfo = this.systemInfoFromConfig();
    const state: IBraviatvState = {
      powerStatus: this.config.state?.powerStatus || (this.config.state?.power === 'on' ? 'active' : this.config.state?.power === 'off' ? 'standby' : 'unknown'),
      available: Boolean(this.config.snapshot || this.config.state || this.config.systemInfo || this.config.host),
      ...this.config.state,
      lastError: errorMessageArg || this.config.state?.lastError,
    };
    return {
      systemInfo,
      state,
      sources: [...(this.config.sources || [])],
      apps: [...(this.config.apps || [])],
      channels: [...(this.config.channels || [])],
      commands: this.config.commands,
      updatedAt: new Date().toISOString(),
    };
  }

  private systemInfoFromConfig(): IBraviatvSystemInfo {
    return {
      ...this.config.systemInfo,
      name: this.config.systemInfo?.name || this.config.name || this.config.host || 'Sony Bravia TV',
      model: this.config.systemInfo?.model || this.config.model,
      serial: this.config.systemInfo?.serial || this.config.serialNumber,
      macAddr: this.config.systemInfo?.macAddr || this.config.macAddress,
      cid: this.config.systemInfo?.cid || this.config.cid || this.config.macAddress || this.config.host,
      product: this.config.systemInfo?.product || 'TV',
    };
  }

  private normalizeSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
    const systemInfo = {
      ...this.systemInfoFromConfig(),
      ...snapshotArg.systemInfo,
    };
    const state = this.normalizeState(snapshotArg.state);
    return {
      ...snapshotArg,
      systemInfo,
      state,
      sources: this.normalizeSources(snapshotArg.sources, 'input'),
      apps: this.normalizeSources(snapshotArg.apps, 'app') as IBraviatvApp[],
      channels: this.normalizeSources(snapshotArg.channels || [], 'channel') as IBraviatvChannel[],
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
    };
  }

  private normalizeState(stateArg: IBraviatvState): IBraviatvState {
    const powerStatus = stateArg.powerStatus || (stateArg.power === 'on' ? 'active' : stateArg.power === 'off' ? 'standby' : 'unknown');
    const power = stateArg.power || this.powerFromStatus(powerStatus);
    let volumeLevel = stateArg.volumeLevel;
    if (typeof volumeLevel === 'number' && volumeLevel > 1) {
      volumeLevel /= 100;
    }
    if (typeof volumeLevel !== 'number' && typeof stateArg.volumePercent === 'number') {
      volumeLevel = stateArg.volumePercent / 100;
    }
    return {
      ...stateArg,
      powerStatus,
      power,
      playback: stateArg.playback || (power === 'off' ? 'off' : 'idle'),
      volumeLevel,
      volumePercent: typeof stateArg.volumePercent === 'number' ? stateArg.volumePercent : typeof volumeLevel === 'number' ? Math.round(volumeLevel * 100) : undefined,
    };
  }

  private normalizeSources(sourcesArg: IBraviatvSource[], typeArg: TBraviatvSourceType): IBraviatvSource[] {
    return sourcesArg.filter((sourceArg) => sourceArg.title && sourceArg.uri).map((sourceArg) => ({
      ...sourceArg,
      type: sourceArg.type || typeArg,
    }));
  }

  private applyVolumeInfo(stateArg: IBraviatvState, volumeInfoArg: IBraviatvVolumeInfo | undefined): void {
    if (!volumeInfoArg) {
      return;
    }
    const volume = typeof volumeInfoArg.volume === 'number' ? volumeInfoArg.volume : Number(volumeInfoArg.volume);
    if (Number.isFinite(volume)) {
      stateArg.volumePercent = volume;
      stateArg.volumeLevel = volume / 100;
    }
    stateArg.muted = volumeInfoArg.mute;
  }

  private applyPlayingInfo(stateArg: IBraviatvState, playingInfoArg: IBraviatvPlayingInfo | undefined): void {
    if (!playingInfoArg) {
      stateArg.mediaTitle = stateArg.mediaTitle || 'Smart TV';
      return;
    }
    stateArg.mediaTitle = playingInfoArg.programTitle || playingInfoArg.title || stateArg.mediaTitle;
    stateArg.mediaContentId = playingInfoArg.uri || playingInfoArg.dispNum || stateArg.mediaContentId;
    stateArg.sourceUri = playingInfoArg.uri || stateArg.sourceUri;
    stateArg.mediaDuration = playingInfoArg.durationSec;
    if (playingInfoArg.uri?.startsWith('extInput:')) {
      stateArg.source = playingInfoArg.title || stateArg.source;
    }
    if (playingInfoArg.uri?.startsWith('tv:')) {
      stateArg.mediaChannel = playingInfoArg.title || playingInfoArg.dispNum;
      stateArg.mediaContentType = 'channel';
    } else if (playingInfoArg.uri) {
      stateArg.mediaContentType = this.inferSourceType(playingInfoArg.uri);
    }
    if (playingInfoArg.startDateTime) {
      const startTime = Date.parse(playingInfoArg.startDateTime);
      if (Number.isFinite(startTime)) {
        stateArg.mediaPosition = Math.max(0, Math.floor((Date.now() - startTime) / 1000));
        stateArg.mediaPositionUpdatedAt = new Date().toISOString();
      }
    }
  }

  private inferSourceType(uriArg: string): TBraviatvSourceType {
    if (uriArg.startsWith('extInput:')) {
      return 'input';
    }
    if (uriArg.startsWith('tv:')) {
      return 'channel';
    }
    return 'app';
  }

  private powerFromStatus(statusArg: string): 'on' | 'off' | 'unknown' {
    const status = statusArg.toLowerCase();
    if (status === 'active') {
      return 'on';
    }
    if (status === 'standby' || status === 'off') {
      return 'off';
    }
    return 'unknown';
  }

  private updateLocalState(stateArg: Partial<IBraviatvState>): void {
    const state = this.config.snapshot?.state || this.config.state || {};
    Object.assign(state, stateArg);
    if (this.config.snapshot) {
      this.config.snapshot.state = state;
      return;
    }
    this.config.state = state;
  }

  private cloneSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
    return JSON.parse(JSON.stringify(snapshotArg)) as IBraviatvSnapshot;
  }

  private errorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }
}
