import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { BaseIntegration } from '../../core/classes.baseintegration.js';
import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
import { RflinkClient } from './rflink.classes.client.js';
import { RflinkConfigFlow } from './rflink.classes.configflow.js';
import { createRflinkDiscoveryDescriptor } from './rflink.discovery.js';
import { RflinkMapper } from './rflink.mapper.js';
import type { IRflinkConfig } from './rflink.types.js';

export class RflinkIntegration extends BaseIntegration<IRflinkConfig> {
  public readonly domain = 'rflink';
  public readonly displayName = 'RFLink';
  public readonly status = 'control-runtime' as const;
  public readonly discoveryDescriptor = createRflinkDiscoveryDescriptor();
  public readonly configFlow = new RflinkConfigFlow();
  public readonly metadata = {
    source: 'home-assistant/core',
    upstreamPath: 'homeassistant/components/rflink',
    upstreamDomain: 'rflink',
    integrationType: 'hub',
    iotClass: 'assumed_state',
    qualityScale: 'legacy',
    requirements: ['rflink==0.0.67'],
    dependencies: [],
    afterDependencies: [],
    codeowners: ['@javicalle'],
    documentation: 'https://www.home-assistant.io/integrations/rflink',
    configFlow: true,
    discovery: {
      manual: true,
      serial: true,
      tcp: true,
      note: 'Home Assistant RFLink is YAML/manual setup. This TypeScript port recognizes explicit manual serial and TCP gateway entries.',
    },
    runtime: {
      type: 'control-runtime',
      polling: 'snapshot/manual',
      services: ['turn_on', 'turn_off', 'set_value', 'open', 'close', 'learn', 'send_command', 'refresh'],
      lineProtocol: 'RFLink 10;protocol;id;switch;command; command lines with optional commandExecutor transport.',
    },
    localApi: {
      implemented: [
        'Manual serial/TCP gateway configuration shape without a serial package dependency',
        'RFLink packet/event typing and basic line packet parsing',
        'RFLink command line shape generation including raw learning lines',
        'Configured/manual snapshot mapping for light, switch, sensor, binary_sensor, and cover entities',
      ],
      explicitUnsupported: [
        'Opening serial ports directly from this package',
        'Owning TCP sockets without an injected commandExecutor',
        'RFLink ACK serialization against live hardware without an injected transport',
      ],
    },
  };

  public async setup(configArg: IRflinkConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
    void contextArg;
    return new RflinkRuntime(new RflinkClient(configArg));
  }

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

export class HomeAssistantRflinkIntegration extends RflinkIntegration {}

class RflinkRuntime implements IIntegrationRuntime {
  public domain = 'rflink';

  constructor(private readonly client: RflinkClient) {}

  public async devices(): Promise<shxInterfaces.data.IDeviceDefinition[]> {
    return RflinkMapper.toDevices(await this.client.getSnapshot());
  }

  public async entities(): Promise<IIntegrationEntity[]> {
    return RflinkMapper.toEntities(await this.client.getSnapshot());
  }

  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
    const unsubscribe = this.client.onEvent((eventArg) => handlerArg(RflinkMapper.toIntegrationEvent(eventArg)));
    return async () => unsubscribe();
  }

  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const snapshot = await this.client.getSnapshot();
    const command = RflinkMapper.commandForService(snapshot, requestArg);
    if (!command) {
      return { success: false, error: `Unsupported RFLink service: ${requestArg.domain}.${requestArg.service}` };
    }
    const result = await this.client.sendCommand(command);
    return { success: result.success, error: result.error, data: result.data };
  }

  public async destroy(): Promise<void> {
    await this.client.destroy();
  }
}
