import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { IFileConfig } from './file.types.js';
import { fileDefaultName, fileProfile } from './file.types.js';

export class FileMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IFileConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: fileProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData),
    });
  }

  public static toSnapshotFromRaw(configArg: IFileConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(fileProfile, snapshotArg);
  }

  public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
    return SimpleLocalMapper.toEntities(fileProfile, snapshotArg);
  }

  public static slug(valueArg: unknown): string {
    return SimpleLocalMapper.slug(valueArg);
  }

  private static normalizeRawData(configArg: IFileConfig, rawDataArg: unknown): unknown {
    if (isRecord(rawDataArg) && 'device' in rawDataArg && 'entities' in rawDataArg) {
      return rawDataArg;
    }

    const rawObject = isRecord(rawDataArg) ? rawDataArg : undefined;
    const metadata = configArg.metadata || {};
    const filePath = stringValue(configArg.filePath) || stringValue(configArg.file_path) || stringValue(rawObject?.filePath) || stringValue(rawObject?.file_path) || stringValue(rawObject?.path) || stringValue(metadata.filePath) || stringValue(metadata.file_path);
    const latestEntry = stringValue(rawObject?.latestEntry) || stringValue(rawObject?.latest_entry) || stringValue(rawObject?.lastLine) || stringValue(rawObject?.last_line) || stringValue(rawObject?.line) || stringValue(rawObject?.value) || primitiveString(rawDataArg);
    if (latestEntry === undefined && !filePath) {
      return rawDataArg;
    }

    const configuredPlatform = stringValue(configArg.platform) || stringValue(rawObject?.platform) || stringValue(metadata.platform) || 'sensor';
    const name = configArg.name || stringValue(rawObject?.name) || (configuredPlatform === 'notify' ? 'File Notify' : fileDefaultName);
    const unit = stringValue(configArg.unitOfMeasurement) || stringValue(configArg.unit_of_measurement) || stringValue(rawObject?.unit_of_measurement);

    return {
      device: {
        id: configArg.uniqueId || filePath || name,
        name,
        manufacturer: fileProfile.manufacturer,
        model: configuredPlatform === 'notify' ? 'Local File Notify' : fileProfile.model,
        protocol: fileProfile.defaultProtocol,
        attributes: {
          filePath,
          configuredPlatform,
          timestamp: configArg.timestamp ?? rawObject?.timestamp,
        },
      },
      entities: [{
        id: 'latest_entry',
        uniqueId: `${fileProfile.domain}_${SimpleLocalMapper.slug(filePath || name)}_latest_entry`,
        name: configuredPlatform === 'notify' ? `${name} Target` : name,
        platform: 'sensor',
        state: latestEntry ?? null,
        available: latestEntry !== undefined || Boolean(filePath),
        writable: false,
        unit,
        attributes: {
          filePath,
          configuredPlatform,
          valueTemplate: configArg.valueTemplate || configArg.value_template,
          contentBytes: rawObject?.contentBytes,
        },
      } satisfies ISimpleLocalEntitySnapshot],
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const primitiveString = (valueArg: unknown): string | undefined => valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg) ? String(valueArg) : undefined;

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

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
