export interface DeepLink {
  url: URL;
  path: string;
  query: URLSearchParams;
}
export function parseDeepLink(value: string, schemes: string[] = []): DeepLink | null {
  try {
    const url = new URL(value);
    if (schemes.length && !schemes.includes(url.protocol.replace(/:$/, ""))) return null;
    return { url, path: url.pathname || "/", query: url.searchParams };
  } catch {
    return null;
  }
}

export interface DeepLinkSource {
  current?(): Promise<string | undefined>;
  subscribe(listener: (url: string) => void): void | (() => void);
}

/** Normalize initial and live native links and ignore malformed/unapproved schemes. */
export function listenDeepLinks(
  source: DeepLinkSource,
  listener: (link: DeepLink) => void,
  schemes: string[] = [],
): () => void {
  let active = true;
  const emit = (value: string) => {
    const link = parseDeepLink(value, schemes);
    if (active && link) listener(link);
  };
  void source.current?.().then((value) => value && emit(value));
  const unsubscribe = source.subscribe(emit);
  return () => {
    active = false;
    unsubscribe?.();
  };
}

export interface PushRegistration {
  token: string;
  platform?: string;
}

export interface PushAdapter {
  permission(): Promise<"granted" | "denied" | "prompt" | "unavailable">;
  requestPermission?(): Promise<"granted" | "denied">;
  register(): Promise<PushRegistration>;
  subscribe?(listener: (notification: unknown) => void): () => void;
}

export class PushNotifications {
  constructor(private readonly adapter: PushAdapter) {}
  async register(): Promise<PushRegistration> {
    let permission = await this.adapter.permission();
    if (permission === "prompt" && this.adapter.requestPermission)
      permission = await this.adapter.requestPermission();
    if (permission !== "granted") throw new Error("WRN-MOBILE-PUSH-PERMISSION-DENIED");
    const registration = await this.adapter.register();
    if (!registration.token.trim()) throw new Error("WRN-MOBILE-PUSH-EMPTY-TOKEN");
    return registration;
  }
  subscribe(listener: (notification: unknown) => void): () => void {
    return this.adapter.subscribe?.(listener) ?? (() => undefined);
  }
}

export interface SecureStorageAdapter {
  get(key: string): Promise<string | null>;
  set(key: string, value: string): Promise<void>;
  remove(key: string): Promise<void>;
}

export class SecureStorage {
  constructor(
    private readonly adapter: SecureStorageAdapter,
    private readonly namespace = "wrnexus",
  ) {}
  #key(key: string): string {
    if (!/^[A-Za-z0-9._-]{1,128}$/.test(key)) throw new TypeError("WRN-MOBILE-STORAGE-KEY");
    return `${this.namespace}:${key}`;
  }
  get(key: string): Promise<string | null> {
    return this.adapter.get(this.#key(key));
  }
  set(key: string, value: string): Promise<void> {
    return this.adapter.set(this.#key(key), value);
  }
  remove(key: string): Promise<void> {
    return this.adapter.remove(this.#key(key));
  }
}
export interface OfflineTask<T = unknown> {
  id: string;
  type: string;
  payload: T;
  createdAt: number;
  attempts: number;
}
export interface OfflineTaskStore {
  load(): Promise<OfflineTask[]>;
  save(tasks: OfflineTask[]): Promise<void>;
}
export function memoryOfflineTaskStore(): OfflineTaskStore {
  let tasks: OfflineTask[] = [];
  return {
    async load() {
      return structuredClone(tasks);
    },
    async save(next) {
      tasks = structuredClone(next);
    },
  };
}
export class OfflineQueue {
  readonly #handlers = new Map<string, (payload: unknown) => Promise<void>>();
  constructor(private readonly store: OfflineTaskStore = memoryOfflineTaskStore()) {}
  process<T>(type: string, handler: (payload: T) => Promise<void>): void {
    this.#handlers.set(type, handler as (payload: unknown) => Promise<void>);
  }
  async add<T>(type: string, payload: T): Promise<OfflineTask<T>> {
    const tasks = await this.store.load();
    const task = { id: crypto.randomUUID(), type, payload, createdAt: Date.now(), attempts: 0 };
    tasks.push(task);
    await this.store.save(tasks);
    return task;
  }
  async sync(limit = 20): Promise<{ completed: number; failed: number }> {
    const tasks = await this.store.load();
    const remaining: OfflineTask[] = [];
    let completed = 0,
      failed = 0;
    for (const task of tasks) {
      if (completed + failed >= limit) {
        remaining.push(task);
        continue;
      }
      const handler = this.#handlers.get(task.type);
      if (!handler) {
        remaining.push(task);
        continue;
      }
      try {
        task.attempts++;
        await handler(task.payload);
        completed++;
      } catch {
        failed++;
        remaining.push(task);
      }
    }
    await this.store.save(remaining);
    return { completed, failed };
  }
  async size(): Promise<number> {
    return (await this.store.load()).length;
  }
}
export interface MobileEnvironment {
  platform: string;
  native: boolean;
  online: boolean;
  userAgent?: string;
}
export function mobileEnvironment(): MobileEnvironment {
  const capacitor = (
    globalThis as typeof globalThis & {
      Capacitor?: { isNativePlatform?: () => boolean; getPlatform?: () => string };
    }
  ).Capacitor;
  const native = capacitor?.isNativePlatform?.() === true;
  return {
    platform: capacitor?.getPlatform?.() ?? "web",
    native,
    online: typeof navigator === "undefined" ? true : navigator.onLine,
    userAgent: typeof navigator === "undefined" ? undefined : navigator.userAgent,
  };
}
