/*
 * Copyright (c) 2023-2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { avSession } from '@kit.AVSessionKit';
import { common } from '@kit.AbilityKit';
import { media } from '@kit.MediaKit';
import { resourceManager } from '@kit.LocalizationKit';
import Logger from '../util/Logger';
import { SONG_List } from '../mock/BackgroundPlayerData';
import { fileIo } from '@kit.CoreFileKit';

const TAG: string = 'BackgroundPlayerFeature';

class PlayList {
  public audioFiles: Array<Song> = [];
}

class Song {
  public name: string;
  public fileUri: string;
  public duration: number;

  constructor(name, fileUri, duration) {
    this.name = name;
    this.fileUri = fileUri;
    this.duration = duration;
  }
}

class PlayerModel {
  public isPlaying: boolean = false;
  public playlist: PlayList = new PlayList();
  public playerIndex: number = 0;
  public player: media.AVPlayer;
  public avSession: avSession.AVSession;
  public prepareCallback: (() => void) | null = null;

  constructor() {
    Logger.info(TAG, `createAVPlayer start`);
    media.createAVPlayer().then((player: media.AVPlayer) => {
      Logger.info(TAG, `createAVPlayer end and initAVPlayer`);
      this.player = player;
      this.initAVPlayer();
      Logger.info(TAG, `createAVPlayer= ${this.player}`);
    }).catch((err: Error) => {
      Logger.error(TAG, `createAVPlayer failed: ${err.message}`);
    })
  }

  initAVPlayer(): void {
    Logger.info(TAG, 'initAVPlayer begin');
    this.player.on('error', (err: Error) => {
      Logger.error(TAG, `player error: ${err.message}`);
    })
    this.player.on('stateChange', (state: string) => {
      Logger.info(TAG, `stateChange() callback is called, state: ${state}`);
      if (state === 'initialized' && this.prepareCallback) {
        Logger.info(TAG, `state is initialized, calling prepare`);
        this.player.prepare().then(() => {
          Logger.info(TAG, `player.prepare done, state= ${this.player.state}`);
        }).catch((err: Error) => {
          Logger.error(TAG, `player.prepare failed: ${err.message}`);
        });
      }
      if (state === 'prepared' && this.prepareCallback) {
        Logger.info(TAG, `dataLoad callback, state= ${this.player.state}`);
        const callback = this.prepareCallback;
        this.prepareCallback = null;
        callback();
      }
    })
    Logger.info(TAG, `initAVPlayer end`);
  }

  createAVSession(context: common.UIAbilityContext): void {
    Logger.debug(TAG, 'createAVSession begin');
    try {
      avSession.createAVSession(context, 'player', 'audio').then((data) => {
        Logger.debug(TAG, 'createAVSession succeed');
        this.avSession = data;
      }).catch((err: Error) => {
        Logger.error(TAG, `createAVSession failed: ${err.message}`);
      });
    } catch (error) {
      Logger.error(TAG, `createAVSession exception: ${(error as Error).message}`);
    }
  };

  destroyAVSession(): void {
    Logger.debug(TAG, 'destroyAVSession begin');
    try {
      this.avSession?.destroy().catch((err: Error) => {
        Logger.error(TAG, `destroyAVSession failed: ${err.message}`);
      });
    } catch (error) {
      Logger.error(TAG, `destroyAVSession exception: ${(error as Error).message}`);
    }
  };

  getPlaylist(callback: () => void): void {
    // generate play list
    Logger.info(TAG, `getAudioAssets begin`);
    this.playlist = new PlayList();
    this.playlist.audioFiles = [];
    this.playlist.audioFiles[0] = new Song(SONG_List[0].name, SONG_List[0].fileUri, SONG_List[0].duration);
    this.playlist.audioFiles[1] = new Song(SONG_List[1].name, SONG_List[1].fileUri, SONG_List[1].duration);
    callback();
    Logger.info(TAG, `getAudioAssets end`);
  }

  preLoad(index: number, context: common.UIAbilityContext, callback: () => void): number {
    Logger.info(TAG, `preLoad ${index}/${this.playlist.audioFiles.length}`);
    if (index < 0 || index >= this.playlist.audioFiles.length) {
      Logger.error(TAG, `preLoad ignored`);
      return 0;
    }
    if (!this.player) {
      Logger.error(TAG, `preLoad ignored, player not initialized`);
      return 0;
    }
    this.playerIndex = index;
    let fileName = this.playlist.audioFiles[index].fileUri;

    Logger.info(TAG, `player.reset`);
    this.player.reset().then(() => {
      Logger.info(TAG, `player.reset done, state= ${this.player.state}`);
      this.prepareCallback = callback;

      // 判断是系统路径还是应用资源文件
      if (fileName.startsWith('system/') || fileName.startsWith('/system/')) {
        // 系统路径,使用 fileIo.open 获取 fd
        let filePath = fileName.startsWith('/') ? fileName : '/' + fileName;
        Logger.info(TAG, `preLoad filePath= ${filePath}`);
        try {
          fileIo.open(filePath, 0o1000000).then((file: fileIo.File) => {
            Logger.info(TAG, `preLoad fd=${file.fd}`);
            this.player.fdSrc = {
              fd: file.fd,
              offset: 0,
              length: -1
            };
          }).catch((err: Error) => {
            Logger.error(TAG, `fileIo.open failed: ${err.message}`);
          });
        } catch (error) {
          Logger.error(TAG, `fileIo.open exception: ${(error as Error).message}`);
        }
      } else {
        // 应用资源文件,使用 resourceManager.getRawFd
        try {
          context.resourceManager.getRawFd(fileName).then((fileDescriptor: resourceManager.RawFileDescriptor) => {
            Logger.info(TAG, `preLoad fd=${fileDescriptor.fd} offset=${fileDescriptor.offset} length=${fileDescriptor.length}`);
            this.player.fdSrc = {
              fd: fileDescriptor.fd,
              offset: fileDescriptor.offset,
              length: fileDescriptor.length
            };
          }).catch((err: Error) => {
            Logger.error(TAG, `getRawFd failed: ${err.message}`);
          });
        } catch (error) {
          Logger.error(TAG, `getRawFd exception: ${(error as Error).message}`);
        }
      }
    }).catch((err: Error) => {
      Logger.error(TAG, `player.reset failed: ${err.message}`);
    })
    Logger.info(TAG, `preLoad end`);
    return 0;
  }

  getDuration(): number {
    Logger.info(TAG, `getDuration playerIndex= ${this.playerIndex}`);
    if (!this.player) {
      return 0;
    }
    if (this.playlist.audioFiles[this.playerIndex].duration > 0) {
      return this.playlist.audioFiles[this.playerIndex].duration;
    }
    Logger.info(TAG, `getDuration state= ${this.player.state}`);
    this.playlist.audioFiles[this.playerIndex].duration = Math.min(this.player.duration, 97615);
    Logger.info(TAG, `getDuration player.duration= ${this.playlist.audioFiles[this.playerIndex].duration} `);
    return this.playlist.audioFiles[this.playerIndex].duration;
  }

  playMusic(flag: number, startPlay: boolean, context: common.UIAbilityContext): void {
    Logger.info(TAG, `playMusic flag= ${flag} startPlay= ${startPlay}`);
    if (!this.player) {
      Logger.error(TAG, `playMusic ignored, player not initialized`);
      return;
    }
    if (startPlay) {
      this.player.play().then(() => {
        Logger.info(TAG, `player.play called player.state= ${this.player.state}`);
      }).catch((err: Error) => {
        Logger.error(TAG, `player.play failed: ${err.message}`);
      })
      this.createAVSession(context);
    }
  }

  pauseMusic(): void {
    if (!this.isPlaying) {
      Logger.info(TAG, `pauseMusic ignored, isPlaying= ${this.isPlaying}`);
      return
    }
    if (!this.player) {
      Logger.error(TAG, `pauseMusic ignored, player not initialized`);
      return
    }
    Logger.info(TAG, `call player.pauseMusic`);
    this.player.pause().then(() => {
      Logger.info(TAG, `player.pause called, player.state= ${this.player.state}`);
    }).catch((err: Error) => {
      Logger.error(TAG, `player.pause failed: ${err.message}`);
    })
  }

  stopMusic(): void {
    if (!this.isPlaying) {
      Logger.info(TAG, `stopMusic ignored, isPlaying= ${this.isPlaying}`);
      return;
    }
    if (!this.player) {
      Logger.error(TAG, `stopMusic ignored, player not initialized`);
      return;
    }
    Logger.info(TAG, 'call player.stop');
    this.player.stop().then(() => {
      this.destroyAVSession();
      Logger.info(TAG, `player.stop called, player.state= ${this.player.state}`);
    }).catch((err: Error) => {
      Logger.error(TAG, `player.stop failed: ${err.message}`);
    })
  };
}

export default new PlayerModel();