import type {
  RouteMatcher,
  DefaultCreateSessionResult,
  DriverData,
  InitialOpts,
  StringRecord,
  ExternalDriver,
  W3CDriverCaps,
} from '@appium/types';
import {BaseDriver} from 'appium/driver';
import {SafariDriverServer} from './safari';
import {desiredCapConstraints, type SafariConstraints} from './desired-caps';
import * as cookieCommands from './commands/cookies';
import * as findCommands from './commands/find';
import * as recordScreenCommands from './commands/record-screen';
import {formatCapsForServer} from './utils';
import {newMethodMap} from './method-map';

const NO_PROXY: RouteMatcher[] = [
  ['GET', new RegExp('^/session/[^/]+/appium')],
  ['POST', new RegExp('^/session/[^/]+/appium')],
  ['POST', new RegExp('^/session/[^/]+/element/[^/]+/elements?$')],
  ['POST', new RegExp('^/session/[^/]+/elements?$')],

  ['DELETE', new RegExp('^/session/[^/]+/cookie$')],
];

type W3CSafariDriverCaps = W3CDriverCaps<SafariConstraints>;

export class SafariDriver
  extends BaseDriver<SafariConstraints, StringRecord>
  implements ExternalDriver<SafariConstraints, string, StringRecord>
{
  static newMethodMap = newMethodMap;

  proxyReqRes: ((...args: any[]) => any) | null = null;
  _screenRecorder: recordScreenCommands.ScreenRecorder | null = null;

  deleteCookies = cookieCommands.deleteCookies;

  findElOrEls = findCommands.findElOrEls;

  startRecordingScreen = recordScreenCommands.startRecordingScreen;
  stopRecordingScreen = recordScreenCommands.stopRecordingScreen;

  private isProxyActive = false;
  private _safari: SafariDriverServer | null = null;

  constructor(opts: InitialOpts = {} as InitialOpts) {
    super(opts);
    this.desiredCapConstraints = desiredCapConstraints;
    this.locatorStrategies = [
      'xpath',
      'tag name',
      'link text',
      'partial link text',
      'css selector',
      // Let these two reach Safari Driver and fail there with a proper error message
      'id',
      'name',
    ];
    this.resetState();
  }

  get safari(): SafariDriverServer {
    if (!this._safari) {
      throw new Error('Safari driver server is not initialized');
    }
    return this._safari;
  }

  override proxyActive(): boolean {
    return this.isProxyActive;
  }

  override getProxyAvoidList(): RouteMatcher[] {
    return NO_PROXY;
  }

  override canProxy(): boolean {
    return true;
  }

  override async createSession(
    w3cCaps1: W3CSafariDriverCaps,
    w3cCaps2?: W3CSafariDriverCaps,
    w3cCaps3?: W3CSafariDriverCaps,
    driverData?: DriverData[],
  ): Promise<DefaultCreateSessionResult<SafariConstraints>> {
    const [sessionId, caps] = await super.createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData);
    this._safari = new SafariDriverServer(this.log);
    try {
      await this.safari.start(formatCapsForServer(caps), {
        reqBasePath: this.basePath,
      });
    } catch (e) {
      await this.deleteSession();
      throw e;
    }
    this.proxyReqRes = this.safari.proxy.proxyReqRes.bind(this.safari.proxy);
    this.isProxyActive = true;
    return [sessionId, caps];
  }

  override async deleteSession(): Promise<void> {
    this.log.info('Ending Safari session');
    await this._screenRecorder?.stop(true);
    await this._safari?.stop();
    this.resetState();

    await super.deleteSession();
  }

  private resetState(): void {
    this._safari = null;
    this.proxyReqRes = null;
    this.isProxyActive = false;
    this._screenRecorder = null;
  }
}

export default SafariDriver;
