(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD (Asynchronous Module Definition)
        define(['firebase/app', 'firebase/database'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // CommonJS (Node.js)
        const { initializeApp } = require('firebase/app');
        const { getDatabase, ref, push, get, set, onChildAdded, remove, onChildRemoved } = require('firebase/database');
        module.exports = factory(initializeApp, getDatabase, ref, push, get, set, onChildAdded, remove, onChildRemoved);
    } else {
        // Global (ブラウザ環境)
        root.FirebaseFunctions = factory(
            root.firebase.initializeApp,
            root.firebase.database.getDatabase,
            root.firebase.database.ref,
            root.firebase.database.push,
            root.firebase.database.get,
            root.firebase.database.set,
            root.firebase.database.onChildAdded,
            root.firebase.database.remove,
            root.firebase.database.onChildRemoved
        );
    }
}(typeof self !== 'undefined' ? self : this, function (initializeApp, getDatabase, ref, push, get, set, onChildAdded, remove, onChildRemoved) {

    class FirebaseFunctions{
        constructor(FIREBASE_CONFIG){              
            // Initialize Firebase
            const APP = initializeApp(FIREBASE_CONFIG); 
            this.DB = getDatabase(APP);
            this.DB_REF_COOKIE =  ref(this.DB, `data/cookie`);
        
            this.__initTipFlg();
            
        }
        
        uploadExpiringCookie(data, EXPIRE_AFTER_X_TIME = 3000){
            var expire = new Date();
            expire.setTime(expire.getTime()+EXPIRE_AFTER_X_TIME);
            
            const DB_REF_DATA =  ref(this.DB, "data/cookie");

            //この関数を使用するとき、様々なデータを扱うだろう
            //例えばログイン状態を一時的に保存するために使われる。
            //他には、遷移先のページで実行させたい動作を保存するかもしれない。
            //(例:一度ログイン画面を経由して、設定画面に移動したい時にGogingToSetting:trueのように使う)
            //様々な種類のデータを扱うため、object型で  data.isloginとかdata.isGoingSettingのように
            //使ったほうが コードが読みやすくなると考えた。そのため、dictionary型を推奨することを
            //console logで表示させる。バグのもとになりそうだからだ。
            if(typeof(data)=="object" && Array.isArray(data) == false){
                //推奨されるデータ型です
            }else{
                this.__showCaution("uploadExpiringCookie",data);
            }
        

            const LIST_DATA = [expire,data];
            const JSON_DATA = JSON.stringify(LIST_DATA);

            set(DB_REF_DATA,JSON_DATA);
        }


        uploadData(rawPath,data){
            rawPath = `data/${rawPath}`;
            var reviewdPath = this.__reviewPath(rawPath);

            const DB_REF_DATA =  ref(this.DB, reviewdPath);
            if(typeof(data)=="string"){
                data = ["json",data];
                //JSONにするには、配列でなければならない。
                //そのため、0番目に識別子jsonをつけて配列にする
            }
            const JSON_DATA = JSON.stringify(data);
            set(DB_REF_DATA,JSON_DATA);
        }

        async downloadExpiringCookie(){
            this.__tellTips("downloadData");

            const DB_REF_DATA = ref(this.DB,"data/cookie");
            try {
                const snapshot = await get(DB_REF_DATA); // await で結果を待機
                if (snapshot.exists()) { // パスワードが登録されていた場合
                    const JSON_DATA = snapshot.val(); // データを格納
                
                    if(typeof(JSON_DATA)=="string"){
                        var parsedData = JSON.parse(JSON_DATA); 
                    }else{
                        var parsedData = JSON_DATA; 
                    }
                    
                    let EXPIRE_DATE = new Date(parsedData[0]); // cookie_dateを格納
                    let CURRENT_DATE = new Date(); // 現在の時刻を取得

                    // cookie_dateから現在時刻までの経過時間をミリ秒で取得
                    let ELAPSED_TIME    = EXPIRE_DATE    - CURRENT_DATE; 
                    // 1000ms(  valid)  = 12:00:03       -  12:00:02
                    //    1ms(  valid)  = 12:00:03:0000  -  12:00:02:999
                    //    0ms(invalid)  = 12:00:03       -  12:00:03
                    //-2000ms(invalid)  = 12:00:03       -  12:00:05

                    if (ELAPSED_TIME > 0) {
                        this.__uploadAndResetInfo();
                        const DICT_DATA =  parsedData[1];
                        return DICT_DATA; // 取得したデータを返す
                    } else {
                        //ログイン情報の有効期限が切れた場合は、falseを返す
                        this.uploadData("data/info",`Cookieの有効期限が切れています。\n有効期限:${EXPIRE_DATE}\n現在時刻:${CURRENT_DATE}\n時差:${ELAPSED_TIME/1000}秒`)
                        await new Promise(resolve => setTimeout(resolve, 2000));
                        return false;
                    }



                } else {
                    console.log('No data available');
                    return null;
                }
            } catch (error) {
                this.__alertMessage(error);
                console.error('Error getting data:', error);
                throw error; // エラーを呼び出し元に伝える
            }
        }
        
        async downloadData(rawPath) {
            this.__tellTips("downloadData");

            rawPath = `data/${rawPath}`;
            var reviewedPath = this.__reviewPath(rawPath);
            const DB_REF_DATA = ref(this.DB, reviewedPath);
        
            try {
                const snapshot = await get(DB_REF_DATA); // await で結果を待機
                if (snapshot.exists()) { // パスワードが登録されていた場合
                    const JSON_DATA = snapshot.val(); // データを格納
                
                    if(typeof(JSON_DATA)=="string"){
                        var parsedData = JSON.parse(JSON_DATA); 
                    }else{
                        var parsedData = JSON_DATA; 
                    }
                    
                    


                    if(Array.isArray(parsedData)){
                        if(parsedData.length >0 && parsedData[0]==="json"){
                            //配列が空だと次の処理が undefined errorとなる。
                            //これを防ぐために parsedData.length>0の条件をはさむ。
                            parsedData = parsedData[1];
                            //JSONは配列やobject型じゃなければパースできない。
                            //そのため、listに直してからパースしている。
                            //データを取り出す時には、元のデータ(文字列や数値)のみ抽出して返す
                        }                 
                    }

                    return parsedData; // 取得したデータを返す
                } else {
                    console.log('No data available');
                    return null;
                }
            } catch (error) {
                this.__alertMessage(error);
                console.error('Error getting data:', error);
                throw error; // エラーを呼び出し元に伝える
            }
        } 

        __uploadAndResetInfo(){
            this.uploadData("data/info","");
        }

        __reviewPath(PATH){
            return PATH.replace(/(\/?data\/)+/, "data/");
            //:  / /は正規表現を宣言
            //:  \/は/のエスケープ文字
        }

        __alertMessage(INFO){
            alert(`Error: yamatoaita@gmail.comにこの文章をお知らせください。\nError info : ${INFO}`)
        }

        __initTipFlg(){
            this.isShowTip = {
                                "downloadData" : true
                            
                            }
        }
        
        __tellTips(METHOD){
            const GREEN = "color:green";
            const RED = "color:red";
            const BLUE = "color:blue";
            const NORMAL = "color:black;font-weight:normal"
            const BOLD  ="font-weight:bold`"

            if(METHOD == "downloadData" && this.isShowTip["downloadData"]){
                this.isShowTip["downloadData"] = false;

                console.log(
    `
    ============================================================================
    |                       %cTip of [downloadData]%c:                             |
    |--------------------------------------------------------------------------|
    |downloadDataメソッドを実行する際は以下のように使います。                  |
    |--------------------------------------------------------------------------|
    |    class ClassName{                                                      |
    |        constructor(){                                                    |
    |            ・・・処理・・・                                              |
    |            this.init(); // データ取得後に実行させたいコードは            |
    |                        // init関数にくくる。                             |
    |        }                                                                 |
    |        %casync%c init(){                                                     |
    |            const DATA = %cawait%c this.FIREBASE_APP.downloadData("cookie");  |
    |            console.log(データが取得後に表示されます‘${DATA}‘)         |
    |            console.log("このログはその後に表示されます")                 |
    |        }                                                                 |
    |    }                                                                     |
    |--------------------------------------------------------------------------|
    |                %cReturnで値を取得したい場合の記載例%c:                       |
    |--------------------------------------------------------------------------|
    |    %casync%c exampleFunction(){                                              |
    |          const VALUE = %cawait%c this.returnFunction();                      |
    |    }                                                                     |
    |    %casync%c returnFunction(){                                               |
    |        const RETURN_VALUE = %cawait%c this.FIREBASE_APP.downloadData("path");|
    |        return RETURN_VALUE;                                              |
    |    }                                                                     |
    |--------------------------------------------------------------------------|
    |                %caddEventListenerで行う場合の記載例%c:                       |
    |--------------------------------------------------------------------------|
    |    setBtnEvent(){                                                        |
    |        const BTN = document.getElementById("btn");                       |
    |        BTN.addEventListener("click", %casync%c ()=>{                         |
    |            const VALUE = %cawait%c this.returnFunction();                    |
    |        })                                                                |
    |    }                                                                     |
    ============================================================================
        ` ,`${GREEN};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,

        `${GREEN};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,

        `${GREEN};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`,
        `${BLUE};${BOLD}`,`${NORMAL}`
    )
            }
        }

        __showCaution(FUNCTION_NAME,ITEM){
            var stack = new Error().stack.replace("Error","");
            stack = stack.replace(/^\s*at FirebaseFunctions.*$/gm, "");


            if(FUNCTION_NAME=="uploadExpiringCookie"){
                alert(`注意 : アップロードしようとしているものはDictionary型ではありません。\n
    uploadExpiringCookie関数は仕様上、Dictionary型を渡すことを推奨します。\n
    渡された値:${ITEM}   データ型:${typeof(ITEM)}\n
    現在の行番号:${stack}`)
            

            }
        }

    }
  
   return FirebaseFunctions;
}));