import { invoke } from '@tauri-apps/api/core';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { message } from '@tauri-apps/plugin-dialog';
import { atom, PrimitiveAtom, useSetAtom } from 'jotai';
import { atomFamily, atomWithRefresh, RESET } from 'jotai/utils';
import { FC, ReactNode, useEffect, useRef } from 'react';

export type Channels = 'a' | 'b';
type ChannelState = {
  playing: boolean;
  playMode: 'shuffle' | 'repeat' | 'repeat-one';
  strength: number;
  maxStrength: number;
  boosting: boolean;
  boostLevel: number;
  maxBoostLevel: number;
};
type PeripheralItem = {
  id: string;
  address: string;
  represent: string | null;
  isUnknown: boolean;
  isConnected: boolean;
  rssi: number | null;
  battery: number | null;
};
type BluetoothState = {
  ready: boolean | null;
  searching: boolean | null;
  connected: string | null;
};

export const BleState = atomWithRefresh<BluetoothState>(async (get) => {
  try {
    const state = await invoke('central_device_state');
    return {
      ready: state.isReady,
      searching: state.isScanning,
      connected: state.connected,
    };
  } catch (e) {
    console.error('[refresh central]', e);
  }
  return {
    ready: null,
    searching: null,
    connected: null,
  };
});
export const DeviceState = atomWithRefresh<PeripheralItem | null>(async (get) => {
  try {
    const state = await invoke('connected_peripheral_state');
    return state;
  } catch (e) {
    console.error('[refresh connected]', e);
  }
  return null;
});
export const FoundPeripherals = atom(
  [] as PeripheralItem[],
  (get, set, item: PeripheralItem | typeof RESET) => {
    if (item === RESET) {
      void set(FoundPeripherals, []);
    } else {
      void set(FoundPeripherals, (prev) => {
        const foundIndex = prev.findIndex((i) => i.id === item.id);
        if (foundIndex !== -1) {
          prev[foundIndex] = item;
        } else {
          prev.push(item);
        }
        return prev;
      });
    }
  },
);
const Channels: Record<Channels, PrimitiveAtom<ChannelState>> = {
  a: atomWithRefresh<ChannelState>(async (get) => {
    try {
      const state = await invoke('channel_a_state');
      return {
        playing: state.isPlaying,
        playMode: state.playMode,
        strength: state.strength,
        maxStrength: state.maxStrength,
        boosting: state.isBoosting,
        boostLevel: state.boostLevel,
        maxBoostLevel: state.maxBoostLevel,
      };
    } catch (e) {
      console.error('[refresh channel a]', e);
    }
    return {
      playing: false,
      playMode: 'repeat-one',
      strength: 0,
      maxStrength: 100,
      boosting: false,
      boostLevel: 0,
      maxBoostLevel: 100,
    };
  }),
  b: atom<ChannelState>(async (get) => {
    try {
      const state = await invoke('channel_b_state');
      return {
        playing: state.isPlaying,
        playMode: state.playMode,
        strength: state.strength,
        maxStrength: state.maxStrength,
        boosting: state.isBoosting,
        boostLevel: state.boostLevel,
        maxBoostLevel: state.maxBoostLevel,
      };
    } catch (e) {
      console.error('[refresh channel b]', e);
    }
    return {
      playing: false,
      playMode: 'repeat-one',
      strength: 0,
      maxStrength: 100,
      boosting: false,
      boostLevel: 0,
      maxBoostLevel: 100,
    };
  }),
};
export const ChannelState = atomFamily((channel: Channels) => Channels[channel]);

const EstimWatchProvider: FC<{ children?: ReactNode }> = ({ children }) => {
  const unlistenBle = useRef<UnlistenFn | null>(null);
  const unlistenDevice = useRef<UnlistenFn | null>(null);
  const unlistenPreipherals = useRef<UnlistenFn | null>(null);
  const unlistenChannelA = useRef<UnlistenFn | null>(null);
  const unlistenChannelB = useRef<UnlistenFn | null>(null);
  const refreshBle = useSetAtom(BleState);
  const refreshDevice = useSetAtom(DeviceState);
  const refreshPeripherals = useSetAtom(FoundPeripherals);
  const refreshChannelA = useSetAtom(ChannelState('a'));
  const refreshChannelB = useSetAtom(ChannelState('b'));

  useEffect(() => {
    (async function () {
      try {
        unlistenBle.current = await listen('central_state_updated', () => refreshBle());
        unlistenDevice.current = await listen('peripheral_connected', () => refreshDevice());
        unlistenPreipherals.current = await listen('peripheral_found', (event) => {
          refreshPeripherals(event.payload);
        });
        unlistenChannelA.current = await listen('channel_a_updated', () => refreshChannelA());
        unlistenChannelB.current = await listen('channel_b_updated', () => refreshChannelB());
        await invoke('activate_central_adapter');
      } catch (e) {
        console.error('[Activate Adapter]', e);
        await message('Fail to activate Bluetooth adapter.', {
          title: 'Bluetooth Error',
          kind: 'error',
        });
      }
    })();

    return () => {
      unlistenBle.current?.();
      unlistenDevice.current?.();
      unlistenPreipherals.current?.();
      unlistenChannelA.current?.();
      unlistenChannelB.current?.();
    };
  }, []);
  return <>{children}</>;
};

export default EstimWatchProvider;
