#!/bin/bash
cd "$(dirname "$0")" || exit
# Commands such as git pull return exit code if there are no changes
# set -e

if [[ -a ".env" ]]; then
    source .env
fi

help-table() {
    local cmd_width=26
    local opt_width=20
    local desc_width=78
    local column="| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n"

    printf "$column" 'Command' 'Option' 'Description'
    echo "|$(printf '%*s' $((cmd_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((opt_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((desc_width + 2)) '' | tr ' ' '-')|"
    # Env
    printf "$column" 'backup-env-files' '[path]' 'Archive and copy env files to target location.'
    printf "$column" 'copy-env' '[env][env]' 'Copy env file.'
    printf "$column" 'create-nextcloud-env' '[env]' 'Create env file for Nextcloud instance.'
    printf "$column" 'create-odoo-env' '[env]' 'Create env file for Odoo instance.'
    printf "$column" 'edit-env' '[env]' 'Open env file in default editor.'
    printf "$column" 'list-env' '' 'List env files.'
    printf "$column" 'load-dotenv' '' 'Restore content of .env from pass entry.'
    printf "$column" 'load-env' '[env]' 'Load and export env file.'
    printf "$column" 'remove-env' '[env]' 'Remove environment config.'
    printf "$column" 'remove-venv' '' 'Remove virtualenv.'
    printf "$column" 'rename-env' '[env][env]' 'Rename env file.'
    printf "$column" 'restore-env-files' '[path]' 'Extract and copy env files from backup file.'
    printf "$column" 'save-dotenv' '' 'Store content of .env in pass entry.'
    printf "$column" 'show-env' '[env]' 'Output content of the env file.'
    # Git
    printf "$column" 'aggregate-git-folders' '[path]' 'Run gitaggregate for the provide repos.yaml.'
    printf "$column" 'add-git-submodule' '[url] [path]' 'Add git submodule.'
    printf "$column" 'checkout' '[version]' 'Checkout Odoo version.'
    printf "$column" 'checkout-git-folder' '' 'Checkout git commit.'
    printf "$column" 'commit-git-folder' '[message][path]' 'Commit all changes in path.'
    printf "$column" 'clean-git-folder' '' 'Clean git folder.'
    printf "$column" 'clone-git-folder' '[submodule][version]' 'Clone git folder listed in the .gitmodules file.'
    printf "$column" 'create-git-feature-branch' '[path]' 'Create feature branch for Odoo module.'
    printf "$column" 'create-git-mig-branch' '[path]' 'Create migration branch for Odoo module.'
    printf "$column" 'download-git-folder' '[grep]' 'Download git folder listed in the .gitmodules file.'
    printf "$column" 'status-git-folder' '[path]' 'Show status for git folder in path.'
    printf "$column" 'list-git-folder' '[grep]' 'List path and url of git folders.'
    printf "$column" 'pull-git-folder' '' 'Pull all git folders listed in the .gitmodules file.'
    printf "$column" 'push-git-folder' '' 'Push all git folders in path.'
    printf "$column" 'remove-git-folders' '' 'Delete all git folders.'
    printf "$column" 'remove-git-submodule' '[path]' 'Remove a git submodule.'
    printf "$column" 'ls-git-folder' '[grep]' 'List git folders path space separated.'
    printf "$column" 'reset-git-folder' '' 'Abort rebase and reset submodules listed in the .gitmodules file.'
    printf "$column" 'stage-git-folder' '[path]' 'Stage all files in git folders in path.'
    printf "$column" 'switch-git-folder' '[version]' 'Switch branch for all git folders listed in the .gitmodules file.'
    printf "$column" 'sync-git-folder' '' 'Switch, stash and pull all git folders.'
    # Docker
    printf "$column" 'archive-docker-tags' '' 'Archive Docker image tags on hub older than 1 year.'
    printf "$column" 'build' '[folder][output]' 'Build Docker image from target folder.'
    printf "$column" 'login-docker' '[user][token]' 'Setup Docker Hub login credentials.'
    printf "$column" 'login-podman' '[user][token]' 'Login into registry with podman.'
    printf "$column" 'container-ps' '' 'List container processes.'
    printf "$column" 'push' '[folder]' 'Publish Docker image from target folder.'
    printf "$column" 'remove' '[name]' 'Remove containers and volumes.'
    printf "$column" 'test-image' '[folder]' 'Test Docker image from target folder.'
    # Vuepress
    printf "$column" 'build-vuepress' '' 'Create Vuepress build.'
    printf "$column" 'install-vuepress' '' 'Install Node build dependencies.'
    printf "$column" 'dev-vuepress' '' 'Start Vuepress development server.'
    printf "$column" 'serve-vuepress' '' 'Serve Vuepress build.'
    # Database
    printf "$column" 'change-uuid' '[env]' 'Change database uuid via xmlrpc.'
    printf "$column" 'clear-assets' '[db]' 'Clear all assets of Odoo database.'
    printf "$column" 'clear-filestore' '[db]' 'Clear local filestore folder. No param will clear all filestores.'
    printf "$column" 'clear-views' '[db]' 'Clear all views of Odoo database.'
    printf "$column" 'cloc-odoo' '[db]' 'Count custom line of codes.'
    printf "$column" 'drop-db' '[db]' 'Drop target Odoo database.'
    printf "$column" 'init-db' '[db]' 'Initialize the Odoo database.'
    printf "$column" 'load-language' '[db][lang]' 'Install language package in Odoo db.'
    printf "$column" 'patch-database' '[db][path]' 'Apply sql file to database.'
    printf "$column" 'reset-views' '[db][key]' 'Execute hard reset on views matching keys.'
    printf "$column" 'set-admin' '[db]' 'Sets the password for the first user in database.'
    printf "$column" 'update-module' '[db][name,path]' 'Update target Odoo module.'
    printf "$column" 'update-module-list' '[db]' 'Update module list of Odoo database.'
    # Revision
    printf "$column" 'checkout-latest-revision' '[version]' 'Checkout the latest revision of the Odoo version.'
    printf "$column" 'checkout-revision' '[revision]' 'Load Odoo revision env var and checkout git folders.'
    printf "$column" 'commit-and-push-revision' '[revision]' 'Commit all changes and tag with current revision.'
    printf "$column" 'create-revision' '[revision]' 'Create new Odoo revision.'
    printf "$column" 'list-revision' '' 'List available Odoo revisions.'
    printf "$column" 'load-latest-revision' '[version]' 'Load the latest revision of the Odoo version.'
    printf "$column" 'load-revision' '[revision]' 'Load env var from specified revision.'
    printf "$column" 'show-revision' '[revision]' 'Show references of Odoo revision.'
    # LLM
    printf "$column" 'commit-with-llm' '' 'Commit with llm generated commit message.'
    printf "$column" 'show-odoo-mcp-config' '' 'Show the Odoo MCP server config.'
    printf "$column" 'update-with-llm' '[glob][prompt]' 'Feed module files with prompt to LLM and apply file changes.'
    # Snippets
    printf "$column" 'create-snippet' '[name]' 'Create snippet from template.'
    printf "$column" 'install-snippet' '[env][path]' 'Install snippet definition.'
    printf "$column" 'disable-snippet' '[env][path]' 'Disable snippet definition.'
    printf "$column" 'lint-snippets' '' 'Run checks for all snippets.'
    printf "$column" 'remove-snippet' '[env][path]' 'Remove snippet definition.'
    printf "$column" 'update-snippet' '[env][path]' 'Update snippet definition.'
    # Setup
    printf "$column" 'activate-venv' '' 'Activate virtualenv.'
    printf "$column" 'init-venv' '' 'Initialize python virtual env.'
    printf "$column" 'disable-mailserver' '[env]' 'Disable mail server settings via xmlrpc.'
    printf "$column" 'install' '' 'Install Odoo requirements in source folder.'
    printf "$column" 'install-odoo-scripts' '' 'Install Odoo scripts.'
    printf "$column" 'install-requirements' '[db][path]' 'Install python packages from requirements.txt.'
    printf "$column" 'list-packages' '' 'List installed python packages.'
    printf "$column" 'init-module' '[db][path,module]' 'Initialize Odoo module.'
    printf "$column" 'set-addons-path' '' 'Set Odoo addons path env variable.'
    # Migration
    printf "$column" 'export-website-data' '[env]' 'Export website data from Odoo database.'
    printf "$column" 'import-csv' '[db][path]' 'Import data from csv. Filename must match PostgreSQL table name.'
    printf "$column" 'import-website-data' '[env]' 'Import website data to Odoo database.'
    # Generate
    printf "$column" 'create-module' '[path]' 'Create new Odoo module from template.'
    printf "$column" 'generate-module-docs' '[path]' 'Generate readme file for module with OCA tools.'
    printf "$column" 'generate-module-model' '[path][model]' 'Generate model in module folder.'
    printf "$column" 'generate-module-inherit' '[path][model]' 'Generate inherited model in module folder.'
    printf "$column" 'generate-module-views' '[path][model]' 'Generate model views in module folder.'
    printf "$column" 'generate-module-security' '[path][model]' 'Generate model access file.'
    printf "$column" 'generate-module-migration' '[path]' 'Generate .'
    printf "$column" 'generate-module-snippet' '[path][ref][model]' 'Generate snippet for referefenced view.'
    printf "$column" 'generate-module-wizard' '[path][model]' 'Generate wizard for a model in module folder.'
    printf "$column" 'generate-module-repo' '[path]' 'Initialize Odoo module repo from template.'
    # Module
    printf "$column" 'lint-module' '[path]' 'Run pylint odoo for module.'
    printf "$column" 'lint-module-repo' '[path]' 'Run pylint odoo for modules in repo folder.'
    printf "$column" 'list-modules' '[path]' 'Get modules in path as bash array.'
    printf "$column" 'pytest-module' '[path]' 'Run module tests with pytest.'
    printf "$column" 'release-module' '[path]' 'Create GitHub release for a module.'
    printf "$column" 'remove-module' '[db][name]' 'Remove target Odoo module.'
    printf "$column" 'test-module' '[path]' 'Test target Odoo module.'
    printf "$column" 'test-modules' '[path]' 'Test target Odoo modules in repo folder.'
    printf "$column" 'translate-module' '[path][lang][db]' 'Generate translation for Odoo module.'
    printf "$column" 'upload-module' '[env][path]' 'Zip and upload Odoo module.'
    printf "$column" 'visualize-dependencies' '[path]' 'Generate visualizations of module dependencies.'
    printf "$column" 'zip-module' '[path]' 'Create zip file for module.'
    # Helper
    printf "$column" 'generate-admin-password' '[pass]' 'Generate hash for Odoo master password.'
    printf "$column" 'generate-ssh-keys' '' 'Generate ssh key pair.'
    printf "$column" 'generate-pg-ssl-keys' '' 'Generate PostgreSQL SSL key material.'
    printf "$column" 'get-addons-path' '' 'Output addons path.'
    printf "$column" 'get-modules' '[path][option]' 'Get list of modules in path. Option is '\''basename'\'' or '\''dirname'\''.'
    printf "$column" 'get-module-version' '[path]' 'Get module version from manifest.'
    printf "$column" 'get-module-checksum' '[path]' 'Get module checksum.'
    printf "$column" 'help' '[grep]' 'Show help for commands.'
    printf "$column" 'info' '' 'Show values of project env vars.'
    printf "$column" 'load-ssh-key' '' 'Load SSH private key from env var.'
    printf "$column" 'update-module-license' '[grep]' 'Update LICENSE file for each matching module.'
    # Run
    printf "$column" 'debug' '[name]' 'Debugg application. Options: source.'
    printf "$column" 'exec' '[name][cmd]' 'Run command in container.'
    printf "$column" 'logs' '[name]' 'Tail container logs. Default is '\''odoo'\''.'
    printf "$column" 'odoocli' '[param]' 'Execute odoocli cli.'
    printf "$column" 'psql' '[db]' 'Start interactive psql shell.'
    printf "$column" 'restart' '[name]' 'Restart container.'
    printf "$column" 'run' '[name][cmd]' 'Run container with command.'
    printf "$column" 'shell' '[db][code]' 'Start interactive odoo shell or run code.'
    printf "$column" 'source' '' 'Source the Python virtual env.'
    printf "$column" 'start' '[name][db]' 'Start application. Options: none, admin, db, mailgate, mailpit, source, odoo, hatch.'
    printf "$column" 'stop' '[name]' 'Stop containers.'
    printf "$column" 'test-xmlrpc' '[env]' 'Test json rpc connection.'
    # Project
    printf "$column" 'lint' '' 'Run precommit for this project.'
    printf "$column" 'list-versions' '' 'List available Odoo versions.'
    printf "$column" 'check-version' '' 'Check if Odoo source is the correct version.'
    printf "$column" 'load-version' '[version]' 'Load git refs from  version folder.'
    printf "$column" 'save-version' '' 'Save git folder refs to version folder.'
    printf "$column" 'template-compose' '' 'Template the Docker compose file.'
    printf "$column" 'template-odoo-rc' '' 'Template the Odoo config file.'
    printf "$column" 'template-repo' '[path]' 'Update the repo folder from template.'
    printf "$column" 'test-project' '[clean]' 'Run tests for this project.'
    printf "$column" 'version' '' 'Show version of required tools.'
    # Performance
    printf "$column" 'record-with-memray' '[name]' 'Record application memory usage with memray. Options: source.'
    printf "$column" 'record-with-py-spy' '[pid]' 'Record and create flamechart for a process.'
    # Docs
    printf "$column" 'update-docs' '' 'Update all project docs.'
    printf "$column" 'update-help-doc' '' 'Write help table to task.md file.'
    printf "$column" 'update-modules-doc' '' 'Update modules docs file.'
    printf "$column" 'update-revisions-doc' '' 'Update revisions doc file.'
    printf "$column" 'update-snippets-doc' '' 'Update snippets doc file.'
    # Upgrade
    printf "$column" 'migrate-module' '[path]' 'Migrate module code from to target Odoo version.'
    printf "$column" 'run-enterprise-upgrade' '[db][version][mode]' 'Run the Odoo Enterprise upgrade scripts. Default mode ist '\''test'\''.'
    printf "$column" 'run-open-upgrade' '[db][version]' 'Run the Odoo OpenUpgrade scripts.'
}

help() {
    echo
    if [[ -n "$1" ]]; then
        help-table | grep -i "$1" | column -t -s'|'
    else
        echo 'task <command> [options]'
        echo
        echo 'commands:'
        echo
        help-table
    fi
    echo
}


# Static env vars

PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]')
ODOO_RC="odoo.conf"
TASK_CONFIG_DIR="$HOME/.config/odoo-build"
export PYTHONPATH="$PWD/odoo" # Required so upgrade-util does not overwrite the odoo namespace
ODOO_VERSION=$(echo "$ODOO_REVISION" | cut -d'.' -f1-2)
CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY"

# Updateable env vars

ORG_NAME=${ORG_NAME:=Mint-System}
ODOO_DATABASE=${ODOO_DATABASE:="$ODOO_VERSION"}
WITHOUT_DEMO=${WITHOUT_DEMO:=False}
ODOO_LANGUAGE=${ODOO_LANGUAGE:=de_CH}
ODOO_PORT=${ODOO_PORT:=8069}
POSTGRES_IMAGE=${POSTGRES_IMAGE:=pgvector/pgvector:pg16-trixie}
POSTGRES_PORT=${POSTGRES_PORT:=5432}
POSTGRES_SSL=${POSTGRES_SSL:=on}
PGSSLMODE=${PGSSLMODE:=prefer}
LOG_LEVEL=${LOG_LEVEL:=info}
CONTAINER_REGISTRY=${CONTAINER_REGISTRY:=mintsystem}
PLATFORM=${PLATFORM:=linux/amd64}
CONTAINER_ENGINE=${CONTAINER_ENGINE:=docker}
COMPOSE_COMMAND=${COMPOSE_COMMAND:=docker compose}
LLM_MODEL=${LLM_MODEL:=llama}
SMTP_SERVER=${SMTP_SERVER:=localhost}
SMTP_PORT=${SMTP_PORT:=1025}
SMTP_SSL=${SMTP_SSL:=False}
EMAIL_FROM=${EMAIL_FROM:=info@yourcompany.com}
MAIL_ALIAS_DOMAIN=${MAIL_ALIAS_DOMAIN:=yourcompany.com}
LIST_DB=${LIST_DB:=True}
CONTAINER_TAG="odoo:$ODOO_REVISION"
ODOO_INIT_LOGIN=${ODOO_INIT_LOGIN:=admin}
ODOO_INIT_PASSWORD=${ODOO_INIT_PASSWORD:=admin}

# Conditional env vars

if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
    COMPOSE_COMMAND="podman-compose"
fi

if [[ "$(uname)" == "Darwin" ]]; then
    OS_RELEASE="Darwin"
    PYTHON_NOTIFY=""
else
    OS_RELEASE=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"')
    PYTHON_NOTIFY="inotify"
fi

if [ "$WITHOUT_DEMO" = "True" ]; then
    ODOO_PARAM="--without-demo=all"
fi

# Check commands

if ! (command -v grep &> /dev/null && grep --version | grep -q GNU) && [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then
    echo 'Error: On Darwin systems, GNU grep must be installed.'
    exit 1
fi

if ! command -v envsubst &> /dev/null; then
    echo 'Error: '\''envsubst'\'' is not available. Install '\''gettext'\'' package.'
    exit 1
fi

# Import commands

clone-taskfile(){
    if [[ ! -d "$HOME/taskfile.build" ]]; then
        echo -e '\033[38;5;214mGit\033[0m: Clone taskfile repo'
        git clone https://git.taskfile.build "$HOME/taskfile.build"
    else
        echo -e '\033[38;5;214mGit\033[0m: Pull taskfile repo'
        git -C "$HOME/taskfile.build" pull
    fi
}

if [[ -d "$HOME/taskfile.build/bin" ]]; then
    for file in "$HOME/taskfile.build/bin/"*; do
        if [[ -f "$file" ]]; then
            source "$file"
        fi
    done
fi

# Help commands

info() {
    set-addons-path
    echo "OS Release: $OS_RELEASE"
    echo "Pass Entry: $PASS_ENTRY"
    echo "Odoo Revision: $ODOO_REVISION"
    echo "Odoo Version: $ODOO_VERSION"
    echo "Odoo Port: $ODOO_PORT"
    echo "Postgres Port: $POSTGRES_PORT"
    echo "Odoo Language: $ODOO_LANGUAGE"
    echo "Container Registry: $CONTAINER_REGISTRY"
    echo "Container Config: $CONTAINER_CONFIG"
    echo "Addons Path: $ADDONS_PATH"
}

version() {
    uv --version
    wkhtmltopdf -V
    if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
        docker -v
        docker compose version
    else
        podman --version
        podman-compose --version
    fi
}

generate-admin-password() {
    if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
    export ODOO_PASSWORD="$1"
    bin/hash-password
}

show-odoo-mcp-config() {
    echo 'Add this config to your IDE:  '
    cat << EOF
{
    "mcpServers": {
        "odoo": {
            "command": "$PWD/venv$ODOO_VERSION/bin/python",
            "args": ["$PWD/images/odoo-mcp/bin/server"],
            "env": {
                "ODOO_URL": "http://localhost:8069",
                "ODOO_DATABASE": "$ODOO_DATABASE",
                "ODOO_USERNAME": "admin",
                "ODOO_PASSWORD": "admin"
            }
        }
    }
}
EOF
}

# Env commands

init-venv() {
    echo "Ensure Python version $(cat .python-version) is installed."
    uv python install

    if [[ ! -d ".venv$ODOO_VERSION" ]]; then
        echo "Init .venv$ODOO_VERSION with $(uv --version)."
        uv venv ".venv$ODOO_VERSION"
    fi
}

activate-venv() {
    if [[ -f  ".venv$ODOO_VERSION/bin/activate" ]]; then
        source ".venv$ODOO_VERSION/bin/activate"
    else
        echo "Could not find .venv$ODOO_VERSION/bin/active. Use init-venv to initalize."
    fi
}

remove-venv() {
    echo "Remove virtualenv venv$ODOO_VERSION."
    rm -rf "venv$ODOO_VERSION"
}

get-addons-path() {
    set-addons-path
    echo "$ADDONS_PATH"
}

template-odoo-rc() {
    echo "Template $ODOO_RC"

    # Load and export vars defined in odoo conf template
    for var in $(grep -oE '\$([A-Z_][A-Z0-9_]*)|\$\{([A-Z_][A-Z0-9_]*)\}' odoo.conf.template); do
      var=${var/\$/}
      var=${var//\{\//}
      export $var
    done

    local ir_config_parameter=${IR_CONFIG_PARAMETER:-""}

    if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
        local git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -w0)
        ir_config_parameter+=$'\n'"git.ssh_private_key = \"$git_ssh_private_key\""
    fi

    if [[ -n "$GIT_SSH_PUBLIC_KEY" ]]; then
        ir_config_parameter+=$'\n'"git.ssh_public_key = \"$GIT_SSH_PUBLIC_KEY\""
    fi

    if [[ -n "$BASE_URL_WEBSITE" ]]; then
        ir_config_parameter+=$'\n'"base_url_website = \"$BASE_URL_WEBSITE\""
    fi

    if [[ -n "$BASE_URL_APP" ]]; then
        ir_config_parameter+=$'\n'"base_url_app = \"$BASE_URL_APP\""
    fi

    if [[ -n "$UPLOADCARE_PUBLIC_KEY" ]]; then
        ir_config_parameter+=$'\n'"uploadcare.public_key = \"$UPLOADCARE_PUBLIC_KEY\""
    fi

    if [[ -n "$UPLOADCARE_SECRET_KEY" ]]; then
        ir_config_parameter+=$'\n'"uploadcare.secret_key = \"$UPLOADCARE_SECRET_KEY\""
    fi

    if [[ -n "$KEYCLOAK_BASE_URL" ]]; then
        ir_config_parameter+=$'\n'"keycloak.base_url = \"$KEYCLOAK_BASE_URL\""
    fi

    if [[ -n "$KEYCLOAK_CLIENT_ID" ]]; then
        ir_config_parameter+=$'\n'"keycloak.client_id = \"$KEYCLOAK_CLIENT_ID\""
    fi

    if [[ -n "$KEYCLOAK_CLIENT_SECRET" ]]; then
        ir_config_parameter+=$'\n'"keycloak.client_secret = \"$KEYCLOAK_CLIENT_SECRET\""
    fi

    if [[ -n "$KEYCLOAK_REALM" ]]; then
        ir_config_parameter+=$'\n'"keycloak.realm = \"$KEYCLOAK_REALM\""
    fi

    export IR_CONFIG_PARAMETER="$ir_config_parameter"

    envsubst < "odoo.conf.template" > "$ODOO_RC"
}

set-addons-path() {
    # Read addons path from git module
    local git_folders_path="$(git config --file .gitmodules --get-regexp path | awk '{ print $2 }' | sort |  tr '\n' ',' |  sed 's/,*$//g')"

    # Remove excluded addons
    local excludes="addons/theme_mint_system addons/company addons/web"
    for exclude in $excludes; do
        git_folders_path=$(echo "$git_folders_path" | sed "s|${exclude},||g")
    done

    # Check if addons dir exist
    for addon in $git_folders_path; do
        if [[ -d "$addon" ]]; then
            echo "Path does not exist: $addon"
            exit 1
        fi
    done

    # Convert to source paths
    local addons_path="$(echo $git_folders_path | sed 's|,odoo|,odoo/addons|g')"

    # Append paths from config env var
    if [[ -n "$ODOO_ADDONS_PATH" ]]; then
        addons_path="${ODOO_ADDONS_PATH},${addons_path}"
    fi

    # Export the addons path to make it available to other functions
    export ADDONS_PATH="$addons_path"
}

load-ssh-key() {
    if [[ -n "$GIT_SSH_PRIVATE_KEY" ]]; then
        echo 'Setup SSH key from env var.'
        local decoded_git_ssh_private_key=$(echo -e "$GIT_SSH_PRIVATE_KEY" | base64 -d)
        mkdir -p ~/.ssh
        echo "$decoded_git_ssh_private_key" > ~/.ssh/id_ed25519
        chmod 600 ~/.ssh/id_ed25519
        eval "$(ssh-agent -s)"
        ssh-add ~/.ssh/id_ed25519 || (echo 'Dumping ~/.ssh/id_ed25519 content:' && cat ~/.ssh/id_ed25519)
    else
        echo 'The SSH key env var is empty.'
        exit 1
    fi
}

# Docs commands

lint() {
    echo "Run pre-commit in $PWD"
    pre-commit run
}

update-help-doc() {
    echo 'Pipe help table into task.md.'
    help-table > task.md
}

update-snippets-doc() {
    ./bin/update-snippets-doc
}

update-revisions-doc() {

    echo 'Update revisions doc file.'

    rm -f "revisions.md"
    echo -e "# Odoo Revisions \n\
A Odoo revision is a snapshot of git references of the Odoo source and modules at a specific date.\n\
For each major release there are multiple revisions.\n" > "revisions.md"

    declare -A revisions
    local path_url="https://github.com/${ORG_NAME}/Odoo-Build/tree/main"

    for file in $(ls revisions/ | sort -rV); do
        local revision="$file"
        local version=$(echo "$revision" | cut -d'.' -f1-2)

        if [[ -z "${revisions[$version]}" ]]; then
            revisions[$version]="## $version"
        fi

        revisions[$version]+=$'\n\n'
        revisions[$version]+="#### $revision"
        revisions[$version]+=$'\n\n```bash\n'
        revisions[$version]+=$(cat "revisions/$file")
        revisions[$version]+=$'\n```'
    done

    for revision in $(echo "${!revisions[@]}" | tr ' ' '\n' | sort -rV); do
        echo -e "${revisions[$revision]}\n" >> "revisions.md"
    done
}

update-modules-doc() {
    echo 'Update module doc file.'
    ./bin/update-modules-doc
}

update-docs() {
    update-modules-doc
    update-revisions-doc
    update-snippets-doc
    update-help-doc
}

update-module-license() {
    if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
    local git_folders=$(list-module "$1")

    for git_folder in $git_folders; do
       echo "Update license file for $git_folder."
       cp templates/module/LICENSE "$git_folder/"
    done
}

template-repo(){
    if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
    local repo_path="$1"

    echo "Copy task file to $repo_path."
    cp "templates/task" "$repo_path"

    echo "Template README.md to $repo_path."
    export REPO_FOLDER=$(basename "$repo_path")
    export REPO_MODEL=${REPO_FOLDER%%_*}
    export REPO_TITLE=$(echo "$REPO_FOLDER" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
    export REPO_NAME=$(echo "$REPO_FOLDER" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g')
    envsubst < "templates/README.md" > "$repo_path/README.md"

    echo "Copy $ODOO_VERSION template files to $repo_path."
    cp -r "templates/$ODOO_VERSION/." "$repo_path"

    echo 'Remove deprecated repo files.'
    rm -f "$repo_path/.flake8"
    rm -f "$repo_path/.isort.cfg"
    rm -f "$repo_path/.pylintrc-mandatory"
}

# Module commands

get-modules() {
    if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi

    local option="$2"
    if [[ -z "$option" ]]; then
        option="dirname"
    fi

    local modules
    if [[ "$option" == "dirname" ]]; then
        modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*([0-9]+\.[0-9]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" |  xargs -r dirname | sort -u | tr "\n" "," | sed 's/,$//')
    fi
    if [[ "$option" == "basename" ]]; then
        modules=$(echo "$1" | tr "," "\n" | xargs -I {} find {} -type f -name "__manifest__.py" | xargs grep -l -iE "version.*([0-9]+\.[0-9]+(\.[0-9]+)?|${ODOO_VERSION}(\.[0-9]+)*)" | xargs -r dirname | xargs -r -I {} basename {} | sort -u | tr "\n" "," | sed 's/,$//')
    fi
    echo "$modules"
}

get-module-version() {
    if [[ -z "$1" ]]; then
        echo '\$1 is empty.'
    else
        # Get version of module
        local version=$(grep -m 1 \"version "$1/__manifest__.py" |  sed "s;';\";g"  | sed "s/,//g" | sed  's/#.*//g')
        version=$(echo "{ $version }" | jq .version | sed 's/"//g' | sed 's/null//g')

        # Set default version
        [[ -z "$version" ]] && version=0.0

        local count_dots=$(echo "$version" | grep -o "\." | wc -l)

        # Check if oca version or enterprise version
        if [[ $count_dots == 2 ]]; then
            version="$ODOO_VERSION.$version"
        fi
        if [[ $count_dots == 1 ]]; then
            version="$ODOO_VERSION.$version"
        fi

        echo "$version"
    fi
}

get-module-checksum() {
    if [[ -z "$1" ]]; then
        echo '\$1 is empty.'
    else
        bin/get-module-checksum "$1"
    fi
}

list-modules() {
    if [[ -z "$1" ]]; then echo '\$1 is empty.'; exit; fi
    local modules=$(get-modules "$1" dirname)
    echo "$modules" | tr ',' '\n'
}

# Container commands

archive-docker-tags() {
    export CONTAINER_CONFIG
    python bin/archive-docker-tags
}

template-compose() {
    echo 'Template compose.yml'

    # Load and export vars defined in compose template
    for var in $(grep -oE '\$\{?[A-Z_][A-Z0-9_]*\}?' compose.yml.template); do
        # Strip $, ${, }
        var=${var#\$\{}    # Remove leading ${
        var=${var%\}}      # Remove trailing }
        var=${var#\$}      # Remove leading $
        export "$var"
    done

    envsubst < "compose.yml.template" > "compose.yml"
}

container-ps() {
    $CONTAINER_ENGINE ps
}

build() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    # Set container tag based on Dockerfile
    local manifest_file
    local latest_tag

    if [[ "$1" == "images/odoo" ]]; then
        manifest_file="images/odoo/Dockerfile"
        latest_tag="odoo:$ODOO_VERSION"
    fi
    if [[ "$1" == "images/odoo-mailgate" ]]; then
        manifest_file="images/odoo-mailgate/Dockerfile"
        CONTAINER_TAG="odoo-mailgate"
        latest_tag="odoo-mailgate:latest"
    elif [[ "$1" == "images/odoo-upgrade" ]]; then
        manifest_file="images/odoo-upgrade/Dockerfile"
        CONTAINER_TAG="odoo-upgrade"
        latest_tag="odoo-upgrade:latest"
    elif [[ "$1" == "images/odoocli" ]]; then
        manifest_file="images/odoocli/Dockerfile"
        CONTAINER_TAG="odoocli"
        latest_tag="odoocli:latest"
    elif [[ "$1" == "images/odoo-mcp" ]]; then
        manifest_file="images/odoo-mcp/Dockerfile"
        CONTAINER_TAG="odoo-mcp"
        latest_tag="odoo-mcp:latest"
    fi

    # Load Odoo revision
    local odoo_date=$(echo "$ODOO_REVISION" | cut -d'.' -f3-)
    source "revisions/$ODOO_REVISION"

    # Select Python version
    case "$ODOO_VERSION" in
        "15.0")
            local python_version="3.9"
            PLATFORM="linux/amd64"
            ;;
        "16.0")
            local python_version="3.11"
            ;;
        "17.0")
            local python_version="3.11"
            ;;
        "18.0")
            local python_version="3.12"
            ;;
        "19.0")
            local python_version="3.13"
            ;;
        *)
            local python_version="3.12"
            ;;
    esac

    local docker_output="--load"
    if [[ -n "$2" ]]; then
        docker_output="$2"
    fi

    # Output configuration
    echo "Python Version: $python_version"
    echo "Platform: $PLATFORM"
    echo "Docker Output: $docker_output"

    echo "Remove container image with tag ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
    docker image rm -f "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"

    if ! docker buildx inspect odoo_builder >/dev/null 2>&1; then
        docker buildx create --name odoo_builder --use
        docker buildx inspect --bootstrap
    else
        docker buildx use odoo_builder
    fi

    echo "Run Docker build ${CONTAINER_REGISTRY}/${CONTAINER_TAG} for ${PLATFORM}"
    SOURCE_DATE_EPOCH=$(git -C odoo log -1 --pretty=%ct) docker buildx build --platform "$PLATFORM" . \
        --file "$manifest_file" \
        --build-arg PYTHON_VERSION="$python_version" \
        --build-arg ODOO_VERSION="$ODOO_VERSION" \
        --build-arg ODOO_DATE="$odoo_date" \
        --build-arg ODOO_ENTERPRISE_REF="$ODOO_ENTERPRISE_REF" \
        --tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" \
        --tag "${CONTAINER_REGISTRY}/${latest_tag}" \
        "$docker_output"
}

load-latest-revision() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
    load-revision "$ODOO_REVISION"
}

checkout-latest-revision() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    ODOO_REVISION=$(list-revision | grep "$ODOO_VERSION" | tail -n1)
    checkout-revision "$ODOO_REVISION"
    check-version
}

container-login() {
    if [[ -n "$1" ]]; then
        CONTAINER_REGISTRY_USERNAME="$1"
    fi
    if [[ -n "$2" ]]; then
        CONTAINER_REGISTRY_PASSWORD="$2"
    fi

    echo "Docker login with username $CONTAINER_REGISTRY_USERNAME."
    echo "$CONTAINER_REGISTRY_PASSWORD" | docker --config "$CONTAINER_CONFIG" login --username "$CONTAINER_REGISTRY_USERNAME" --password-stdin
}

login-podman() {
    if [[ -n "$1" ]]; then
        CONTAINER_REGISTRY_USERNAME="$1"
    fi
    if [[ -n "$2" ]]; then
        CONTAINER_REGISTRY_PASSWORD="$2"
    fi

    echo "Podman login with username $CONTAINER_REGISTRY_USERNAME."
    podman login --username "$CONTAINER_REGISTRY_USERNAME" --password "$CONTAINER_REGISTRY_PASSWORD" docker.io
}

test-image() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    if [[ "$1" == "images/odoo-upgrade" ]]; then
        CONTAINER_TAG="odoo-upgrade"
    elif [[ "$1" == "images/odoocli" ]]; then
        CONTAINER_TAG="odoocli"
    fi

    if [[ "$CONTAINER_TAG" =~ ^odoo: ]]; then
        generate-ssh-keys
        export GIT_SSH_PRIVATE_KEY
        export GIT_SSH_PUBLIC_KEY
        export PATH=./images/odoo/bin/:$PATH
        export LOCAL_PATH="./tmp"
        mkdir -p "$LOCAL_PATH"

        echo -e "\033[38;5;214mTEST\033[0m: Run git clone addons"
        local server_tools_git_ref=$(curl -s "https://api.github.com/repos/OCA/server-tools/branches/$ODOO_VERSION" | grep '"sha":' | head -n 1 | awk -F '"' '{print $4}')
        export ADDONS_GIT_REPOS="https://github.com/OCA/server-tools.git#${ODOO_VERSION}#${server_tools_git_ref}"
        # This will remove all SSH keys at the end
        ./images/odoo/bin/clone-git-addons
        rm -rf "$LOCAL_PATH/github.com" # Cleanup for next run

        ODOO_VERSION="18.0"  # Example value

        echo -e "\033[38;5;214mTEST\033[0m: Check addons path regex with version: $ODOO_VERSION"
        echo "'version': '1.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
        echo "'version': '1.1'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
        echo "'version': '2.5'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
        echo "'version': '2.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
        echo "'version': '13.0.1.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✗ No match (expected)" || echo "✓ No match is ok"
        echo "'version': '14.0.2.1.1'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✗ No match (expected)" || echo "✓ No match is ok"
        echo "'version': '${ODOO_VERSION}.0.0.0'" | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Matched" || echo "✗ Match missing"
        echo '"Version": "1.0"' | grep -iE "version.*([0-9]+\.[0-9]+|[0-9]+\.[0-9]+\.[0-9]+|${ODOO_VERSION}(\.[0-9]+)+)" && echo "✓ Must match" || echo "✗ Match missing"

        echo -e "\033[38;5;214mTEST\033[0m: Install python package."
        export PYTHON_INSTALL="prometheus-client,pyjwt"
        ./images/odoo/bin/install-python-packages

        echo -e "\033[38;5;214mTEST\033[0m: Sync python project."
        cat > tmp/pyproject.toml << 'EOF'
[project]
name = "test-project"
version = "0.1.0"
description = "Test project for uv sync"
dependencies = [
    "requests>=2.28.0",
    "click>=8.0.0",
]

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.uv]
dev-dependencies = [
    "pytest>=7.0.0",
    "black>=23.0.0",
]
EOF
        ./images/odoo/bin/sync-python-project
        uv pip list

        echo -e "\033[38;5;214mTEST\033[0m: Run set-addons-path"
        export TEST_ADDONS_DIR="./templates,./themes,./enterprise,/gugus"
        export ODOO_VERSION="18.0"
        source ./images/odoo/bin/set-addons-path
        echo $ADDONS_PATH | grep "./templates"
    fi

    if [[ "$CONTAINER_TAG" =~ ^odoo-upgrade ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Show help of odoo-upgrade."
        docker run -it mintsystem/odoo-upgrade --help
    fi

    if [[ "$CONTAINER_TAG" =~ ^odoocli ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Show help of odoocli."
        docker_odoocli() {
            docker run -it \
                -e ODOO_URL="$ODOO_URL" -e ODOO_DATABASE="$ODOO_DATABASE" -e ODOO_USERNAME="$ODOO_USERNAME" -e ODOO_PASSWORD="$ODOO_PASSWORD" \
                mintsystem/odoocli "$@"
            }
        docker_odoocli --help
    fi
}

push() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local latest_tag

    if [[ "$1" == "images/odoo" ]]; then
        latest_tag="odoo:$ODOO_VERSION"
    elif [[ "$1" == "images/odoo-mailgate" ]]; then
        CONTAINER_TAG="odoo-mailgate"
        latest_tag="odoo-mailgate:latest"
    elif [[ "$1" == "images/odoo-upgrade" ]]; then
        CONTAINER_TAG="odoo-upgrade"
        latest_tag="odoo-upgrade:latest"
    elif [[ "$1" == "images/odoocli" ]]; then
        CONTAINER_TAG="odoocli"
        latest_tag="odoocli:latest"
    elif [[ "$1" == "images/odoo-mcp" ]]; then
        CONTAINER_TAG="odoo-mcp"
        latest_tag="odoocli:latest"
    fi

    echo "Tag container image ${CONTAINER_REGISTRY}/${CONTAINER_TAG} with ${CONTAINER_REGISTRY}/${latest_tag}"
    $CONTAINER_ENGINE tag "${CONTAINER_REGISTRY}/${CONTAINER_TAG}" "${CONTAINER_REGISTRY}/${latest_tag}"

    if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
        echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        docker --config "$CONTAINER_CONFIG" push "${CONTAINER_REGISTRY}/${latest_tag}"
    fi

    if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
        echo "Push container image to ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        podman push "${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        podman push "${CONTAINER_REGISTRY}/${latest_tag}"
    fi
}

upload-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Check if path is root folder
    local modules="$2"
    if [[ ! -f "$2/__manifest__.py" ]]; then
        echo 'Identified as root folder'
        modules="$modules/*"
    fi

    for module in $modules; do

        if [[ -f "$module/__manifest__.py" ]]; then

            # Create module zip
            zip-module "$module"

            # Get path to zip file and upload url
            local version=$(get-module-version "$module")
            local module_name=$(basename "$module")
            local file_path="tmp/$module_name-$version.zip"
            load-env "$1"
            local nextcloud_upload_url="$NEXTCLOUD_URL/remote.php/dav/files/$NEXTCLOUD_USERNAME/Odoo-Apps/"

            # Upload zip file
            curl -u "$NEXTCLOUD_USERNAME:$NEXTCLOUD_PASSWORD" -T "$file_path" "$nextcloud_upload_url"
            echo "File $file_path uploaded to $nextcloud_upload_url."

            UPLOADS+="  - name: $module_name\n"
            UPLOADS+="    version: $version\n"
        fi
    done

    echo 'odoo_apps:'
    echo -e "$UPLOADS"
}

logs() {
    local container="$1"
    container=${container:="odoo"}
    $CONTAINER_ENGINE logs --follow "$container"
}

# Script commands

install-odoo-scripts() {
    echo 'Installing Odoo scripts with user flag.'
    curl -L https://ansible.build/odoo_scripts/install | bash -s -- --user
}

run-enterprise-upgrade() {
    if [[ -z "$1" ]]; then
        echo '$1/database is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/version is empty.'
        exit
    fi

    local database="$1"
    local odoo_target_version="$2"
    local mode="test"
    if [[ -n "$3" ]]; then
        # Options: test, production
        mode="$3"
    fi
    local new_database="${database}_${odoo_target_version}"

    echo "Execute Odoo upgrade script in $mode mode for $database with version $odoo_target_version."
    echo "The database will be restored as $new_database."
    export PGHOST=localhost
    export PGUSER=odoo
    export PGPASSWORD=odoo
    python <(curl -s https://upgrade.odoo.com/upgrade) "$mode" -d "$database" -t "$odoo_target_version" -r "$new_database"
    echo 'Upgrade finished successfully.'
}

run-open-upgrade() {
    if [[ -z "$1" ]]; then
        echo '$1/database is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2/version is empty.'
        exit
    fi

    local database="$1"
    local odoo_target_version="$2"
    local new_database="${database}_${odoo_target_version}"

    echo "Get the current version of the database."

    echo "Checkout the Odoo version 15.0..."

    echo "Run the upgrade script for 15.0..."

    echo "Checkout the Odoo version 16.0..."

    echo "Run the upgrade script for 16.0..."

    echo "Checkout the Odoo version 17.0..."

    echo "Run the upgrade script for 17.0..."

    echo "Checkout the Odoo version 18.0..."

    echo "Run the upgrade script for 18.0..."
}

# Install commands

install() {
    init-venv
    activate-venv

    if [[ ! -d "$HOME/taskfile.build" ]]; then
        echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo"
        git clone https://github.com/janikvonrotz/taskfile.build.git ~/taskfile.build
    else
        echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo"
        git -C ~/taskfile.build pull
    fi

    # OS specific installations
    if [[ "$OS_RELEASE" =~ ^(Debian|Ubuntu|Pop!_OS)$ ]]; then

        echo -e "\033[38;5;214mApt\033[0m: Update apt repo"
        sudo apt update

        echo -e "\033[38;5;214mApt\033[0m: Install python-ldap and psycopg2 build dependencies"
        sudo apt-get install -y libsasl2-dev libldap2-dev libssl-dev libpq-dev python3-dev

        echo -e "\033[38;5;214mApt\033[0m: Install xmllint, ripgrep and gettext"
        sudo apt install -y libxml2-utils ripgrep gettext

    elif [[ "$OS_RELEASE" =~ ^(Fedora Linux)$ ]]; then

        echo -e "\033[38;5;214mDnf\033[0m: Update dnf repo"
        sudo dnf update

        echo -e "\033[38;5;214mDnf\033[0m: Install psycopg2 build dependencies"
        sudo dnf install -y postgresql-devel

        echo -e "\033[38;5;214mApt\033[0m: Install ripgrep and gettext"
        sudo dnf install -y ripgrep gettext

    elif [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then

        echo 'Welcome daring Mac user. Let'\''s try our best to get this working.'
        echo -e "\033[38;5;214mBrew\033[0m: Install required packages with brew."
        brew install gettext libsasl2 openldap openssl libxml2 grep ripgrep

    elif [[ "$OS_RELEASE" =~ ^(Arch Linux|SteamOS)$ ]]; then

        echo 'Arch is the best'

        echo -e "\033[38;5;214mPacman\033[0m: Install python-ldap and psycopg2 build dependencies"
        sudo pacman -S --noconfirm base-devel postgresql-devel

        echo -e "\033[38;5;214mApt\033[0m: Install ripgrep"
        sudo pacman -S --noconfirm ripgrep

    else
        echo "The operating system $OS_RELEASE is not supported."
        exit 1
    fi

    # Install wkhtmltopdf
    if [[ "$OS_RELEASE" =~ ^(Arch Linux|Darwin|Debian|Fedora Linux|Ubuntu|Pop!_OS|SteamOS)$ ]]; then

        # Source: https://gist.github.com/faniska/37f896d5e9de5fee925925d7caf3cb9e
        local installed_version=$(wkhtmltopdf --version 2>&1 | sed -E 's/^wkhtmltopdf +([0-9.]+).*/\1/')
        if [[ "$installed_version" != "0.12.4" ]]; then
            echo -e "\033[38;5;214mCurl\033[0m: Install wkhtmltopdf"
            curl -L -O https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
            tar xvf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
            sudo mv wkhtmltox/bin/wkhtmlto* /usr/bin/
            rm -rf wkhtmltox wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
        else
            echo 'wkhtmltopdf version is already 0.12.4'
        fi
    fi

    # Odoo Build requirements
    echo -e "\033[38;5;214mPython\033[0m: Install Odoo Build development tools"
    uv pip install -r requirements.txt

    # Odoo requirements
    echo -e "\033[38;5;214mPython\033[0m: Install Odoo and dependencies."
    cd odoo
    uv pip install -e .
    grep -v "python-ldap" requirements.txt | uv pip install -r -
    cd ..

    # Install inotify
    echo -e "\033[38;5;214mPython\033[0m: Install $PYTHON_NOTIFY."
    if [[ -n "$PYTHON_NOTIFY" ]]; then
        uv pip install "$PYTHON_NOTIFY"
    fi
    if [[ "$PYTHON_NOTIFY" == "inotify" ]] && ! sudo grep -q "fs.inotify.max_user_watches=524288" /etc/sysctl.d/99-sysctl.conf; then
        sudo bash -c 'echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.d/99-sysctl.conf'
        sudo sysctl -p /etc/sysctl.d/99-sysctl.conf
        echo -e "\033[38;5;214mSystem\033[0m: Updated fs.inotify.max_user_watches to 524288"
    fi

    # Fixes for Odoo versions
    if [[ "$ODOO_VERSION" == "14.0" ]] ; then
        echo -e "\033[38;5;214mPython\033[0m: Fix shebang for odoo binary."
        sed -i '1s|.*|#!'"$PWD"'/.venv14.0/bin/python|' .venv14.0/bin/odoo
    fi

}

# Process commands

start() {
    set-addons-path
    if [[ "$POSTGRES_SSL" == "on" ]]; then
        generate-pg-ssl-keys
    fi

    template-compose

    # Use default database if second param is not given
    local database="$2"
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
    fi

    if [[ -z "$1" ]]; then
        echo "Start all container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        echo 'Open http://localhost:8069 url in your browser.'
        $COMPOSE_COMMAND up -d
    fi

    if [[ "$1" =~ "admin" ]]; then
        echo 'Open http://localhost:8000 url in your browser.'
        open-url-with-delay "http://localhost:8000" 2 & $COMPOSE_COMMAND up -d admin
    fi

    if [[ "$1" =~ "db" ]]; then
        $COMPOSE_COMMAND up -d db
    fi

    if [[ "$1" =~ "mailgate" ]]; then
        echo 'Send emails to: smtp://localhost:587'
        $COMPOSE_COMMAND up -d mailgate
    fi

    if [[ "$1" =~ "mailpit" ]]; then
        echo 'Open http://localhost:8025 url in your browser.'
        open-url-with-delay "http://localhost:8025" 2 & $COMPOSE_COMMAND up -d mailpit
    fi

    if [[ "$1" =~ "source" ]]; then
        template-odoo-rc
        local port=8069
        while [[ $port -le 8079 ]]; do
            if ! lsof -i:$port >/dev/null 2>&1; then
                break
            fi
            port=$((port + 1))
        done

        echo "Open http://localhost:$port url in your browser."
        open-url-with-delay "http://localhost:$port/web?debug=1&db=$database" 2 &
        odoo --database="$database" --config "$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
    fi

    if [[ "$1" =~ "hatch" ]]; then
        template-odoo-rc
        local port=8069
        while [[ $port -le 8079 ]]; do
            if ! lsof -i:$port >/dev/null 2>&1; then
                break
            fi
            port=$((port + 1))
        done

        echo "Open http://localhost:$port url in your browser."
        open-url-with-delay "http://localhost:$port/web?debug=1&db=$database" 2 &
        odoo --database="$database" --config "$ODOO_RC" \
            --http-port="$port" --dev=all --log-level="$LOG_LEVEL"
    fi

    if [[ "$1" =~ "odoo" ]]; then
        echo "Start Odoo container ${CONTAINER_REGISTRY}/${CONTAINER_TAG}"
        echo 'Open http://localhost:8069 url in your browser.'
        open-url-with-delay "http://localhost:8069" 2 & $COMPOSE_COMMAND up -d odoo
    fi
}

debug() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    set-addons-path

    local port=8069

    if [[ "$1" =~ "source" ]]; then
        # Disable file validation to avoid frozen modules warning
        export PYDEVD_DISABLE_FILE_VALIDATION=1

        echo 'Starting Odoo with the debugger on port 5678.'
        echo "Attach a debugpy debugger and open http://localhost:$port url in your browser."

        python -m debugpy --listen 5678 --wait-for-client $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
    fi
}

run() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    template-compose

    echo "Run '$1' container with command '$2'."
    $COMPOSE_COMMAND run --rm "$1" "$2" "$3"
}

exec() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local container="$1"
    shift
    local cmd="$@"

    echo "Execute command '$cmd' with $CONTAINER_EXEC_PARAM in container '$container'."
    $CONTAINER_ENGINE exec $CONTAINER_EXEC_PARAM "$container" $cmd
}

record-with-py-spy() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local pid="$1"
    local output_file="tmp/speedscope-capture.json"
    local speedscope_url="https://www.speedscope.app/"

    mkdir -p tmp
    echo "source task source; py-spy record --pid $pid --output $output_file --format speedscope" | sudo bash --

    open-with-os "$speedscope_url"
}

record-with-memray() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    set-addons-path

    local port=8069
    local output_file="tmp/memray-capture.bin"

    if [[ "$1" =~ "source" ]]; then

        echo 'Starting Odoo with memray.'
        echo "Open http://localhost:$port url in your browser."

        mkdir -p tmp
        rm "$output_file"
        python3 -m memray run -o "$output_file" $(which odoo) --database="$DATABASE" --config "$ODOO_RC" \
            --http-port="$port" --addons-path="$ADDONS_PATH" --dev=all --log-level="$LOG_LEVEL"
        python3 -m memray flamegraph "$output_file"
        open-with-os "tmp/memray-flamegraph-output.html"
    fi
}

remove() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND down -v
    fi

    if [[ "$1" =~ "db" ]]; then
        $COMPOSE_COMMAND rm -f -s -v db
    fi

    if [[ "$1" =~ "admin" ]]; then
        $COMPOSE_COMMAND rm -f -s -v admin
    fi

    if [[ "$1" =~ "odoo" ]]; then
        $COMPOSE_COMMAND rm -f -s -v odoo
    fi

    if [[ "$1" =~ "mailpit" ]]; then
        $COMPOSE_COMMAND rm -f -s -v mail
    fi

    if [[ "$1" =~ "mailgate" ]]; then
        $COMPOSE_COMMAND rm -f -s -v mailgate
    fi
}

restart() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND restart
    fi

    if [[ "db|mail|odoo" =~ $1 ]]; then
        $COMPOSE_COMMAND restart "$1"
    fi
}

stop() {
    template-compose

    if [[ -z "$1" ]]; then
        $COMPOSE_COMMAND stop
    fi

    $COMPOSE_COMMAND stop "$1"
}

generate-ssh-keys() {
    if [[ -z "$GIT_SSH_PRIVATE_KEY" ]]; then
        echo 'Generate SSH private and public keys.'
        mkdir -p tmp
        if [[ ! -f "tmp/ed25519" ]]; then
            ssh-keygen -t ed25519 -C admin@example.com -f tmp/ed25519 -N ''
        fi
        GIT_SSH_PRIVATE_KEY=$(cat tmp/ed25519 | base64 -w0)
        GIT_SSH_PUBLIC_KEY=$(cat tmp/ed25519.pub)
    fi
}

generate-pg-ssl-keys() {
    local keymaterial_path="tmp/postgres-odoo"
    if [[ -z "$PG_SSL_CA_CRT" ]] || [[ -z "$PG_SSL_SERVER_CRT" ]] || [[ -z "$PG_SSL_SERVER_KEY" ]]; then
        echo 'Generate PostgreSQL SSL/TLS certificates.'
        mkdir -p "$keymaterial_path"

        if [[ ! -f "$keymaterial_path/ca.crt" ]]; then
            openssl genrsa -out "$keymaterial_path/ca.key" 4096
            chmod 600 "$keymaterial_path/ca.key"

            openssl req -x509 -new -nodes -key "$keymaterial_path/ca.key" -sha256 -days 3650 \
                -out "$keymaterial_path/ca.crt" -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=Root CA"
        fi

        if [[ ! -f "$keymaterial_path/server.crt" ]]; then
            openssl genrsa -out "$keymaterial_path/server.key" 2048
            openssl req -new -key "$keymaterial_path/server.key" -out "$keymaterial_path/server.csr" \
                -subj "/C=CH/ST=State/L=Locality/O=Organization/CN=localhost"
            openssl x509 -req -in "$keymaterial_path/server.csr" -CA "$keymaterial_path/ca.crt" \
                -CAkey "$keymaterial_path/ca.key" -CAcreateserial -out "$keymaterial_path/server.crt" \
                -days 365 -sha256

            chmod 600 "$keymaterial_path/server.key"
            chmod 644 "$keymaterial_path/server.crt"
            sudo chown 999:999 "$keymaterial_path/server.key"
            sudo chown 999:999 "$keymaterial_path/server.crt"
        fi
    fi
}

test-project() {
    if [[ "$1" == "clean" ]]; then
        echo -e "\033[38;5;214mTEST\033[0m: Cleanup test environment."
        drop-db test
        remove odoo
        git -C oca/partner-contact clean -df
        git -C oca/partner-contact checkout .
        remove-env test
    else
        version
        info
        set -e

        echo -e "\033[38;5;214mTEST\033[0m: Clone module repo with https"
        (git clone -b "$ODOO_VERSION" https://github.com/OCA/partner-contact.git oca/partner-contact) || true

        echo -e "\033[38;5;214mTEST\033[0m: Start container environment."
        export BROWSER_OPEN=false
        start db,mail

        echo -e "\033[38;5;214mTEST\033[0m: Clone Odoo addons."
        generate-ssh-keys
        export GIT_SSH_PRIVATE_KEY
        export GIT_SSH_PUBLIC_KEY
        export ADDONS_GIT_REPOS="https://github.com/OCA/server-env.git#${ODOO_VERSION},https://github.com/OCA/server-tools.git#${ODOO_VERSION},https://github.com/Mint-System/Odoo-Apps-Server-Tools.git#${ODOO_VERSION}"
        run odoo clone-git-addons

        echo -e "\033[38;5;214mTEST\033[0m: Init Odoo database."
        export ODOO_ADDONS_PATH="/mnt/oca/partner-contact"
        export ODOO_DATABASE=test
        export WITHOUT_DEMO=False
        export ODOO_INIT_LOGIN=test
        export ODOO_INIT_PASSWORD=test
        export ODOO_INIT_LANG=de_CH
        export ODOO_INIT_ADDONS=server_environment_ir_config_parameter,module_auto_update,prometheus_exporter,mail_server_filter,contacts,web_enterprise
        export PYTHON_INSTALL=prometheus-client,manifestoo
        run odoo init-db

        echo -e "\033[38;5;214mTEST\033[0m: Start Odoo container."
        export ODOO_MAIL_SMTP_HOST=mail
        export ODOO_MAIL_SMTP_PORT=1025
        export ODOO_MAIL_SMTP_ENCRYPTION=none
        export SERVER_WIDE_MODULES=module_change_auto_install
        # export SESSION_DB_URI=postgres://odoo:odoo@db/test
        export MODULE_AUTO_INSTALL_DISABLED=odoo_test_xmlrunner
        export AUTO_UPDATE_MODULES=True
        export AUTO_UPDATE_TRANSLATIONS=True
        export AUTO_UPDATE_MODULES_LIST=True
        export MAIL_DEFAULT_FROM="test"
        export BROWSER_OPEN=false
        export TEST_ADDONS_DIR=/mnt/oca/partner-contact
        export TEST_INCLUDE=partner_firstname
        start odoo
        TIMEOUT=60
        ELAPSED=0
        until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
            if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
                echo "Odoo did not start within $TIMEOUT seconds."
                docker logs odoo
                exit 1
            fi
            echo 'Waiting for Odoo to be ready...'
            sleep 3
            ELAPSED=$((ELAPSED + 3))
        done

        echo -e "\033[38;5;214mTEST\033[0m: Init repos.yml file."
        cat << EOF > tmp/repos.yml
github.com/Mint-System/Odoo-Apps-Partner-Contact:
    remotes:
        mint_system: https://github.com/Mint-System/Odoo-Apps-Partner-Contact.git
    merges:
        - mint_system ${ODOO_VERSION}
    target: mint_system ${ODOO_VERSION}
EOF
        docker cp tmp/repos.yml odoo:/var/lib/odoo/git/repos.yml

        echo -e "\033[38;5;214mTEST\033[0m: Init OCA module."
        run odoo init-module partner_firstname

        echo -e "\033[38;5;214mTEST\033[0m: Setup and check RPC credentials."
        export ODOO_URL="http://localhost:8069"
        export ODOO_USERNAME=test
        export ODOO_PASSWORD=test
        if ! show-env test; then
            create-odoo-env test
        fi
        show-env test
        test-xmlrpc test

        echo -e "\033[38;5;214mTEST\033[0m: Install, update, disable and remove snippet."
        install-snippet test snippets/base.view_partner_form.show_type.xml
        update-snippet test snippets/base.view_partner_form.show_type.xml
        disable-snippet test snippets/base.view_partner_form.show_type.xml
        remove-snippet test snippets/base.view_partner_form.show_type.xml

        echo -e "\033[38;5;214mTEST\033[0m: Check module auto update."
        sed -i 's/First name/Firstname/g' oca/partner-contact/partner_firstname/models/res_partner.py
        restart odoo
        until curl --silent --fail "http://localhost:8069/web/login" &> /dev/null; do
            echo 'Waiting for Odoo to be ready...'
            sleep 3
        done
        docker logs odoo
        docker logs odoo 2>&1 | grep "Run click-odoo-update."

        echo -e "\033[38;5;214mTEST\033[0m: Check translations auto update."
        sed -i 's/Nachname/Familienname/g' oca/partner-contact/partner_firstname/i18n/de.po
        restart odoo

        echo -e "\033[38;5;214mTEST\033[0m: Drop the test database."
        drop-db test

        echo -e "\033[38;5;214mTEST\033[0m: Prepare container for testing."
        exec odoo setup-tests
        exec odoo ls /var/lib/odoo/git/github.com/Mint-System/Odoo-Apps-Partner-Contact

        echo -e "\033[38;5;214mTEST\033[0m: Run module tests."
        CONTAINER_EXEC_PARAM="-w /mnt/oca/partner-contact"
        exec odoo run-tests

        echo -e "\033[38;5;214mTEST\033[0m: Run module upgrade."
        mkdir -p oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0
        cat << EOF > oca/partner-contact/partner_firstname/migrations/${ODOO_VERSION}.2.0.0/post-migration.py
import logging
from odoo.upgrade import util

_logger = logging.getLogger(__name__)


def migrate(cr, version):
    env = util.env(cr)

    partners = env["res.partner"].search([])
    for partner in partners:
        partner.name += "!"

    _logger.info("Updated %s partners", len(partners))
EOF
        sed -i "/\"version\":/c\    \"version\": \"${ODOO_VERSION}.2.0.0\"," oca/partner-contact/partner_firstname/__manifest__.py
        odoo update-module partner_firstname 2>&1 | grep "module partner_firstname: Running migration" || true
    fi
}

# Interactive Shell commands

shell() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi
    local code="$2"

    set-addons-path
    if [[ -n "$code" ]]; then
        echo "$code" | odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
    else
        odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
    fi
}

psql() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    $CONTAINER_ENGINE exec -it db psql "postgres://odoo:odoo@localhost:5432/$database"
}

# Database commands

init-db() {
    local odoo_init_addons="${ODOO_INIT_ADDONS:-"web"}"
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    set-addons-path
    template-odoo-rc

    echo "Initialize database $database with $odoo_init_addons."
    if [[ -n "$ODOO_PARAM" ]]; then
        odoo -d "$database" -i "$odoo_init_addons" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE" "$ODOO_PARAM"
    else
        odoo -d "$database" -i "$odoo_init_addons" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"
    fi
}

import-csv() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local file_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        file_path="$1"
    fi

    local file_name=$(basename "$file_path")
    echo "Importing $file_name to ${database}..."
    $CONTAINER_ENGINE cp "$file_path" db:/tmp/
    $CONTAINER_ENGINE exec db psql "postgres://odoo:odoo@localhost:5432/$database" -c "\copy $file_name FROM '/tmp/$file_name' DELIMITER ',' CSV HEADER;"
}

drop-db() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$database\";"
}

load-language() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local language="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        language="$1"
    fi
    set-addons-path

    echo "Setup language $language for database $database:"
    odoo -d "$database" -u all --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$language"
}

set-admin() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "UPDATE res_users SET active=true,password='\$pbkdf2-sha512\$25000\$JuScEwIg5JzTGqNUivFeqw\$yWTOcix2Afr3XGP2NPY7w4w49e9vpsu14NRndDYXAkbtMF4zkrmx6inVsoLl0zZY30xI/0GzhwonWsK9TUmjWA' WHERE id=2;"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost:5432/$database" -c "SELECT login, password FROM res_users WHERE id=2;"
}

clear-assets() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    echo "Delete these assets for $database:"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "select id,name from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c "delete from ir_attachment where res_model='ir.ui.view' and name like '%assets_%';"
}

clear-views() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    echo 'Archive views with prefix '\''mint_system'\''.'
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "SELECT id,name from ir_ui_view WHERE name like '%mint_system%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "UPDATE ir_ui_view SET active=false WHERE name like '%mint_system%';"

    echo 'Archive views with prefix '\''Odoo Studio:'\''.'
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "SELECT id,name from ir_ui_view WHERE name like '%Odoo Studio:%';"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "UPDATE ir_ui_view SET active=false WHERE name like '%Odoo Studio:%';"

    echo "Delete all active views for $database in $CONTAINER :"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "DELETE FROM report_layout WHERE view_id IN (SELECT id FROM ir_ui_view WHERE active = TRUE);"
    $CONTAINER_ENGINE exec -i db psql "postgres://odoo:odoo@localhost/$database" -c  "WITH RECURSIVE views_to_delete AS (SELECT id, inherit_id FROM ir_ui_view WHERE active = TRUE AND inherit_id IS NULL UNION ALL SELECT v.id, v.inherit_id FROM ir_ui_view v JOIN views_to_delete p ON v.inherit_id = p.id) DELETE FROM ir_ui_view WHERE id IN (SELECT id FROM views_to_delete);"
}

clear-filestore() {
    echo "Remove directory $HOME/.local/share/Odoo/filestore/$1"
    rm -rf "$HOME/.local/share/Odoo/filestore/$1"
}

disable-mailserver() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    load-env "$1"
    images/odoocli/bin/odoocli --method 'write' --model 'ir.mail_server' --field 'active' --value False --domain "[]"
    images/odoocli/bin/odoocli --method 'write' --model 'fetchmail.server' --field 'active' --value False --domain "[]"
}

change-uuid() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    load-env "$1"
    local uuid=$(uuidgen)
    echo "Set uuid $uuid for $1."
    images/odoocli/bin/odoocli --method 'write' --model 'ir.config_parameter' --domain "[('key', '=', 'database.uuid')]" --field 'value' --value "'$uuid'"
}

update-module-list() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    set-addons-path
    echo "env['ir.module.module'].update_list()" | odoo shell --database "$1" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

patch-database() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local filename="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        filename="$1"
    fi

    echo "Apply patch $filename to Database $database."
    cat "$filename" | $CONTAINER_ENGINE exec -i db psql "postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost/$database"
    echo 'Patching database succeeded'
}

# Report commands

cloc-odoo() {
    local database="$1"
    if [[ -z "$database" ]]; then
        database="$ODOO_DATABASE"
    fi

    set-addons-path
    odoo cloc -d "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}

visualize-dependencies() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    mkdir -p tmp
    ./bin/odoo-module-dependencies "$1"
    open-with-os tmp/odoo-module-dependencies.html
    cat tmp/odoo-module-dependencies.mmd
}

# Setup commands

install-requirements() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local file_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        file_path="$1"
    fi

    local file_name=$(basename "$file_path")
    if ! [[ "$file_name" =~ .*requirements\.txt$ ]]; then
        echo '$1 must point to requirements.txt file.'
        exit
    fi

    local container_running=$($CONTAINER_ENGINE container inspect -f '{{.State.Running}}' odoo)
    if [[ "$container_running" == "true" ]]; then
        echo 'Installing python packages in container...'
        $CONTAINER_ENGINE exec -u root odoo python -m pip install -r "/mnt/$file_path"
    else
        echo 'Installing python packages in virutal env...'
        uv pip install -r "$file_path"
    fi
}

list-packages() {
    uv pip list
}

generate-module-repo() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local repo_path="$1"
    local repo_name=$(basename "$repo_path")

    # If repo already exists, create new branch
    if [[ -d "$repo_path/.git" ]]; then
        echo "Create new branch $repo_name in $repo_path"
        cd "$repo_path" || exit
        git switch --orphan -c "$ODOO_VERSION"

        echo "Remove all files from $repo_path"
        rm -rf "$repo_path/*"
    fi

    echo "Copy template files from templates/$ODOO_VERSION to $repo_path"
    cp "templates/$ODOO_VERSION/*" "$repo_path"
}

lint-module-repo() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local module_paths=$(get-modules "$MODULE_PATH")

    for module_path in ${module_paths//,/ }; do
        lint-module "$module_path"
    done
}

# Module commands

init-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local module_path="$2"

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        module_path="$1"
    fi

    # Check if module path is a module repo
    if [[ -f "$module_path/README.md" ]]; then

        # Look for manifest files in repo folder
        # Get parent folder name of manifest files
        # Create a comma separated string of folder names
        local modules=$(get-modules "$module_path" basename)
    else
        local modules=$(basename "$module_path")
    fi

    set-addons-path
    echo "Initialize module $modules on ${database}..."
    odoo -d "$database" -i "$modules" --config "$ODOO_RC" \
        --addons-path="$ADDONS_PATH" --stop-after-init --no-http
}

migrate-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local module_path="$1"
    local source_odoo_version=$(get-module-version $module_path | cut -d'.' -f1,2)
    local directory=$(dirname "$module_path")
    local module=$(basename "$module_path")

    # if [[ "$ODOO_VERSION" == "17.0" ]]; then
    #     echo "Migrate views of module $module_path to ${ODOO_VERSION}..."
    #     odoo --database "$DATABASE" -i "$module_path" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --load=base,web,views_migration_17 --stop-after-init
    # fi

    # if [[ "$ODOO_VERSION" == "18.0" ]]; then
    #     echo "Run Odoo upgrade_code command for $module_path from ${source_odoo_version}..."
    #     odoo upgrade_code --addons-path "$MODULE" --from "$source_odoo_version"

    #     echo "Replace manifest version $source_odoo_version with $ODOO_VERSION."
    #     sed -i -E "s/$source_odoo_version/$ODOO_VERSION/g" "$module_path/__manifest__.py"
    # else

    echo "Run odoo-module-migrator for $module with source $source_odoo_version and target ${ODOO_VERSION}..."
    odoo-module-migrate --directory $(dirname "$module_path") --modules $(basename "$module_path") \
        --init-version-name $source_odoo_version --target-version-name $ODOO_VERSION --no-commit
}

update-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local module_name=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        module_name=$(basename "$1")
    fi

    set-addons-path
    echo "Updating module $module_name on ${database}..."
    if [[ -n "$ODOO_PARAM" ]]; then
        odoo --database "$database" --update "$module_name" \
            --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http "$ODOO_PARAM"
    else
        odoo --database "$database" --update "$module_name" \
            --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http
    fi
}

translate-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local module_path="$1"
    local module_name=$(basename "$1")
    local language="$2"
    local database="$3"

    if [[ -z "$2" ]]; then
        language="$ODOO_LANGUAGE"
    fi

    if [[ -z "$3" ]]; then
        database="$ODOO_DATABASE"
    fi

    set-addons-path
    echo "Generate $language tranlsation file for $module_name on ${database}..."
    mkdir -p "$module_path/i18n/"
    odoo -d "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --modules "$module_name" -l "$language" --i18n-export "$module_path/i18n/$language.po"

    # If lang is de_CH then rename the file to de.po.
    if [[ "$language" == "de_CH" ]]; then
        mv "$module_path/i18n/$language.po" "$module_path/i18n/de.po"
    fi
}

lint-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    echo "Set file and folder permissions on $1"
    find "$1" -type d -exec chmod u=rwx,go=rx {} \;
    find "$1" -type f -exec chmod u=rw,go=r {} \;

    echo 'Update index.html'
    rst2html5 "$1/README.rst"  "$1/static/description/index.html"

    pushd .
    cd "$1"

    echo "Run pre-commit in $PWD"
    pre-commit run --all-files # --show-diff-on-failure --color=always

    echo 'Stage changes'
    git  add .

    popd
}

test-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local module=$(basename "$1")

    set-addons-path
    echo "Testing module $module..."
    odoo -d "$ODOO_DATABASE" -i "$module" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-tags /"$module_name"
}

test-modules() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local modules=$(get-modules "$1" basename)
    set-addons-path

    echo "Collect requirements.txt files in $1."
    find "$1" -type f -name "requirements.txt" -exec uv pip install -r {} \;

    echo "Collect test-requirements.txt files in $1."
    find "$1" -type f -name "test-requirements.txt" -exec uv pip install -r {} \;

    depends_modules=$(manifestoo --select-addons-dir "$1" list-depends --separator=,)
    echo "Setup test database with "$ODOO_LANGUAGE" lang and depends modules: $depends_modules"
    odoo -d "$ODOO_DATABASE" -i "$depends_modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --load-language "$ODOO_LANGUAGE"

    echo "Testing modules: $modules"
    odoo -d "$ODOO_DATABASE" -i "$modules" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --stop-after-init --no-http --test-enable
}

pytest-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local module_path="$1"

    echo "Setup custom tmp/odoo.conf for pytest."
    set-addons-path
    local odoo_config="tmp/odoo.conf"
    echo "[options]" > "$odoo_config"
    echo "addons_path=$ADDONS_PATH" >> "$odoo_config"
    echo "db_host = localhost" >> "$odoo_config"
    echo "db_password = odoo" >> "$odoo_config"
    echo "db_user = odoo" >> "$odoo_config"

    echo "Run pytest with odoo-http option..."
    pytest -s --odoo-http --odoo-config="$odoo_config" --odoo-database="$ODOO_DATABASE" "$module_path"
}

remove-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local module_name=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        module_name=$(basename "$1")
    fi

    set-addons-path
    echo "Remove module $module_name"
    echo "self.env['ir.module.module'].search([('name', '=', '$module_name')]).button_immediate_uninstall()" |
    odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

create-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local module_name=$(basename "$1")
    local parent_dir=$(dirname "$1")

    echo "Scaffolding module $module_name in ${parent_dir}..."
    odoo scaffold "$module_name" "$parent_dir" -t templates/module
}

generate-module-docs() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local module_path="$1"
    local manifest_path="$module_path/__manifest__.py"

    echo 'Setup readme folder'
    mkdir -p "$module_path/readme"
    cp -n -r "templates/module/readme" "$module_path"

    echo 'Update DESCRIPTION.rst'
    export MODULE_SUMMARY=$(python3 -c "import ast; d=ast.literal_eval(open('$manifest_path').read()); print(d.get('summary', '').replace('\n', ' ').strip())")
    envsubst < templates/module/readme/DESCRIPTION.md > "$module_path/readme/DESCRIPTION.md"

    echo 'Update CONTRIBUTORS.rst'
    export GIT_USER_NAME="$(git config user.name)"
    export GIT_USER_EMAIL="$(git config user.email)"
    envsubst < templates/module/readme/CONTRIBUTORS.md > "$module_path/readme/CONTRIBUTORS.md"

    echo 'Copy icon.png'
    cp templates/module/static/description/icon.png "$module_path/static/description/icon.png"

    echo 'Copy LICENSE'
    cp templates/module/LICENSE "$module_path/"

    echo "Generate OCA readme for $ORG_NAME org."
    local repo_url=$(git -C "$(dirname $module_path)" remote get-url origin)
    local repo_name=$(basename "${repo_url%.git}")
    oca-gen-addon-readme --repo-name=$repo_name --branch=$ODOO_VERSION --org-name=$ORG_NAME --addon-dir=$module_path
}

generate-module-migration() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    local module_path="$1"
    local module_version=$(get-module-version $module_path)

    echo "Create migrations folder for version $module_version."
    mkdir -p "$module_path/migrations/$module_version"

    echo "Copy pre-migrate template."
    cp "templates/migrations/pre-migrate.py" "$module_path/migrations/$module_version" 
}

generate-module-security() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    local module_name=$(basename "$1")
    local parent_dir=$(dirname "$1")
    local model_dot_name="$2"
    local model_name=$(echo "$model_dot_name" | tr '.' '_')

    local access_file="$parent_dir/$module_name/security/ir.model.access.csv"
    mkdir -p "$parent_dir/$module_name/security"

    if [[ ! -f "$access_file" ]]; then
        echo "Init security file: $access_file"
        echo "id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink" > "$access_file"
    fi

    # id
    # name
    # model_id:id
    # group_id:id
    # perm_read
    # perm_write
    # perm_create
    # perm_unlink

    echo "Add model access to $access_file"
    echo "access_${model_name}_user,access.$model_dot_name.user,model_${model_name},base.group_user,1,0,0,0" >> "$access_file"
    echo "access_${model_name}_manager,access.$model_dot_name.manager,model_${model_name},base.group_erp_manager,1,1,1,1" >> "$access_file"
}

generate-module-model() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute model template'
    export MODEL_DOT_NAME="$2" # res.partner
    export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
    export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2  | sed 's/[^_]\+/\L\u&/g') # Partners
    export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
    export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
    envsubst < templates/model.py > "$module_path/models/$MODEL_NAME.py"
    echo "from . import $model_name" >> "$module_path/models/__init__.py"
    echo "File "$module_path/models/$MODEL_NAME.py created.
}

generate-module-inherit() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute model template'
    export MODEL_DOT_NAME="$2" # res.partner
    export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
    export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2  | sed 's/[^_]\+/\L\u&/g') # Partners
    export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
    export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
    envsubst < templates/inherit.py > "$module_path/models/$MODEL_NAME.py"
    echo "from . import $MODEL_NAME" >> "$module_path/models/__init__.py"
    echo "File "$module_path/models/$MODEL_NAME.py created.
}

generate-module-views() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi
    local module_path="$1"
    local module_name=$(basename "$1")

    echo 'Substitute view template'
    mkdir -p "$module_path/views"
    export MODEL_DOT_NAME="$2" # res.partner
    export MODEL_NAME=$(echo "$MODEL_DOT_NAME" | tr '.' '_') # res_partner
    export NAME=$(echo "$MODEL_DOT_NAME" | cut -d"." -f2  | sed 's/[^_]\+/\L\u&/g') # Partners
    export MODEL_DESCRIPTION=$(echo "$MODEL_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g') # Res Partner
    export MODEL_CAMEL_NAME=$(echo "$MODEL_DESCRIPTION" | sed 's/ //g') # ResPartner
    envsubst < templates/model_views.xml > "$module_path/views/${model_name}_views.xml"
    echo "Update file $module_name/__manifest__.py with:"
    echo -e "\t\"views/${model_name}_views.xml\","
}

generate-module-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi
    if [[ -z "$3" ]]; then
        echo '$3 is empty.'
        exit
    fi
    local module_path="$1" # addons/sale_workflow/sale_helm

    echo 'Substitute snippet template'
    export MODEL_DOT_NAME="$3" # sale.order
    export VIEW_REF="$2" # sale.view_order_form
    export MODEL_NAME=${MODEL_DOT_NAME//\./_}
    export MODULE_NAME=$(basename "$1")
    export SRC_MODULE_NAME=$(echo "$VIEW_REF" | cut -d"." -f1)
    export XML_ID=$(echo "$VIEW_REF" | cut -d"." -f2)
    mkdir -p "$module_path/views"
    envsubst < templates/model_snippet.xml > "$module_path/views/${MODEL_NAME}_views.xml"
    echo "Update file $MODULE_NAME/__manifest__.py with: \"views/${MODEL_NAME}_views.xml\""
}

release-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local version=$(get-module-version "$1")
    cd "$1" || exit
    local last_tag=$(git describe --tags)
    local release_notes=$(git log "$last_tag"..HEAD --oneline)

    echo "Tag with v${version}..."
    git tag -a "v$version" -m "v$version"

    echo 'Release notes:'
    echo "$release_notes"
}

zip-module() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    # Get version and module name
    local version=$(get-module-version "$1")
    local module=$(basename "$1")

    # Copy module to tmp folder
    echo "Copy module $module version $version to tmp folder"
    local module_name="$module-$version"
    local module_path="tmp/$module_name"
    rm -rf "$module_path"
    mkdir -p "$module_path"
    cp -r "$1/." "$module_path"
    find "$module_path" -type d -exec chmod u=rwx,go=rx {} \;
    find "$module_path" -type f -exec chmod u=rw,go=r {} \;

    # Configure and zip module
    echo 'Remove auto install option'
    sed -i'' -e "s/'auto_install': True,/'auto_install': False,/" "$module_path/__manifest__.py"
    echo 'Remove pycache and hidden folders'
    find "$module_path" | grep -E "(\.git|__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
    echo "Zip module $module"
    cd tmp || exit
    rm -f "$module_name".zip
    zip -q -r "$module_name".zip "$module_name"
    cd .. || exit
}

# Snippet commands

reset-views() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    local database="$1"
    local key=$(basename "$2")

    # Use default database if second param is not given
    if [[ -z "$2" ]]; then
        database="$ODOO_DATABASE"
        key=$(basename "$1")
    fi

    set-addons-path
    echo "Reset views for key $key"
    echo "self.env['ir.ui.view'].search([('key', 'ilike', '$key')]).reset_arch(mode='hard')" |
    odoo shell --database "$database" --config "$ODOO_RC" --addons-path="$ADDONS_PATH" --no-http
}

create-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    export INHERIT_ID=$(echo "$1" | cut -d. -f1-2)
    local file="snippets/$1.xml"

    echo "Create snippet from template: $file"
    envsubst '$INHERIT_ID' < templates/snippet.xml > "$file"
}

install-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="mint_system.${file%.xml}"
    local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local type=$(echo 'cat //data/@type' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local model=$(echo 'cat //data/@model' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local priority=$(echo 'cat //data/@priority' | xmllint --shell "$2" | awk -F'[="]' '!/>/{print $(NF-1)}')
    local module_name=$(echo "$inherit_id" | cut -d. -f 1 )
    local xml_id=$(echo "$inherit_id" | cut -d. -f 2 )

    # Set default values
    : ${type:='qweb'}

    load-env "$1"
    echo "Search for view with name $module_name.$xml_id"
    inherit_id=$(images/odoocli/bin/odoocli --method "read" --model "ir.model.data" --field "res_id" --domain "[('name', '=', '$xml_id'), ('module', '=', '$module_name')]")
    echo "Found view id $inherit_id"

    echo "Apply view $name"
    images/odoocli/bin/odoocli --method 'create' --model 'ir.ui.view' --value "{
        'name': '$name',
        'type': '$type',
        'model': '$model',
        'mode': 'extension',
        'priority': $priority,
        'inherit_id': $inherit_id,
        'arch_base': '''$(cat "$2")'''
    }"
}

update-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="mint_system.${file%.xml}"

    load-env "$1"
    echo "Update view with name $name"
    images/odoocli/bin/odoocli --method 'write' --model 'ir.ui.view' --domain "[('name', '=', '$name')]" --field 'arch_base' --value "'''$(cat "$2")'''"
}

disable-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="mint_system.${file%.xml}"

    load-env "$1"
    echo "Disable view with name $name"
    images/odoocli/bin/odoocli --method 'write' --model "ir.ui.view" --domain "[('name', '=', '$name')]" --field "active" --value "False"
}

remove-snippet() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Generate view name from filename
    local file=$(basename "$2")
    local name="mint_system.${file%.xml}"

    load-env "$1"
    echo "Remove view with name $name"
    images/odoocli/bin/odoocli --method "unlink" --model "ir.ui.view" --domain "[('name', '=', '$name')]"
}

lint-snippets() {

    echo 'Lint snippets files...'

    local files="snippets/*.xml"

    for file in $files; do

        # Access various snippet parameters
        local filename=$(basename "$file")
        local snippet_module=$(echo "$filename" | cut -d. -f1)
        local report=$(echo "$filename" | cut -d. -f2)
        local edit=$(echo "$filename" | cut -d. -f3)
        local inherit_id=$(echo 'cat //data/@inherit_id' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
        local type=$(echo 'cat //data/@type' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
        local model=$(echo 'cat //data/@model' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
        local priority=$(echo 'cat //data/@priority' | xmllint --shell "$file" | awk -F'[="]' '!/>/{print $(NF-1)}')
        local module_name=$(echo "$inherit_id" | cut -d. -f 1 )
        local xml_id=$(echo "$inherit_id" | cut -d. -f 2 )

        # Check filename
        local count_dots=$(echo "$filename" | grep -o "\." | wc -l)
        if [[ "$count_dots" -ge 4 ]]; then
            echo "Linting failed for $file"
            echo "Dots count: $count_dots"
            echo "  Filename: $snippet_module.$report"
            exit 1
        fi

        # Check if inherit id matches the filename
        if [[ -n "$inherit_id" ]]; then
            if [[ "$inherit_id" != "$snippet_module.$report" ]]; then
                echo "Linting failed for $file"
                echo " Inherit: $inherit_id"
                echo "Filename: $snippet_module.$report"
                exit 1
            fi
        fi

        # Check if priority is set
        if [[ -z "$priority" ]] && [[ -n "$inherit_id" ]]; then
            echo "Linting failed for $file"
            echo 'No priority is set.'
            exit 1
        fi
    done
    echo 'No problems with snippets found.'
}

# Git commands

add-git-submodule() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi
    if [[ -z "$2" ]]; then
        echo '$2 is empty.'
        exit
    fi

    # Add to .gitmodules
    git submodule add -f -b "$ODOO_VERSION" "$1" "$2"

    # Ensure branch is Odoo version
    git -C "$2" switch "$ODOO_VERSION"

    # Save version
    save-version

    # Restore staged submodule files
    git restore --staged .gitmodules "$2"

    # Remove from working tree
    # rm -rf .git/modules/"$2" || true
    # git rm --cached "$2" || true
}

aggregate-git-folders() {
    if [[ -z "$1" ]]; then
        echo '$1 is empty.'
        exit
    fi

    gitaggregate -c "$1" --expand-env
}

ls-git-folder() {
    if [[ -n "$1" ]]; then
        echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | grep "$1" | sort)
    else
        echo $(git config --file .gitmodules --get-regexp url | sed -e 's/submodule.//g' -e 's/.url.*//g' | sort)
    fi
}

list-git-folder() {
    # Define column widths
    local path_width=35
    local url_width=50
    local branch_width=8

    # Print table header
    printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "PATH" "URL" "BRANCH"
    echo "|$(printf '%*s' $((path_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((url_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((branch_width + 2)) '' | tr ' ' '-')|"

    local git_folders=$(ls-git-folder "$1")
    for git_folder in $git_folders; do
        local url=$(git config --file .gitmodules --get "submodule.$git_folder.url")
        url=${url#git@github.com:}
        url=${url%.git}
        local branch=$(git -C "$git_folder" branch --show-current 2>/dev/null || echo "detached")
        printf "| %-${path_width}s | %-${url_width}s | %-${branch_width}s |\n" "$git_folder" "$url" "${branch:-detached}"
    done
}

remove-git-folders() {
    local git_folders=$(ls-git-folder)

    echo "Delete: $git_folders"
    read -p "Are you sure you want to delete these git folders? (yes/N): " confirm
    if [[ "${confirm,,}" != "yes" ]]; then
        echo 'Aborted.'
        return 1
    fi

    for git_folder in $git_folders; do
        echo "Delete git folder $git_folder."
        rm -rf "$git_folder"
    done
}

remove-git-submodule() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi

    echo 'Deinit submodule'
    git submodule deinit -f "$1"

    echo 'Remove submodule from work tree'
    git rm -f "$1"

    echo 'Remove submodule from .git folder'
    rm -rf ".git/modules/$1"

    echo 'Remove submodule from .gitmodules file'
    git config --file .gitmodules --remove-section submodule.addons/website

    save-version
}

clone-git-folder() {
    local git_folders=$(ls-git-folder "$1")
    for git_folder in $git_folders; do

        local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path)
        local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url)
        local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow)
        local git_folder_branch=$(git config --file .gitmodules --get submodule."$git_folder".branch)

        if [[ "$shallow" = "true" ]]; then
            echo "Clone git folder $git_folder (shallow) with $git_folder_branch branch"
            git -C "$git_folder" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
            git clone --depth 1 --no-single-branch "$git_folder_url" "$git_folder_path"
        else
            echo "Clone git folder $git_folder with $git_folder_branch branch"
            git clone --branch="$git_folder_branch" "$git_folder_url" "$git_folder_path"
        fi
    done
}

download-git-folder() {
    local git_folders=$(ls-git-folder "$1")
    for git_folder in $git_folders; do

        local git_folder_path=$(git config --file .gitmodules --get submodule."$git_folder".path)
        local git_folder_url=$(git config --file .gitmodules --get submodule."$git_folder".url)
        local git_folder_branch=$(git config --file .gitmodules --get submodule."$git_folder".branch)

        # Convert git url to http url.
        git_folder_url="$(echo $git_folder_url | sed 's|git@|https://|g' | sed 's|m:|m/|g' | sed 's|\.git||g')"

        # Get commit ref from .gitmodule.csv
        local commit_ref=$(grep "$git_folder_path" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)

        echo "Download $git_folder_url to $git_folder_path."
        mkdir -p "$git_folder_path"
        curl -L -o "$git_folder_path/archive.tar.gz" \
            "$git_folder_url/archive/$commit_ref.tar.gz"

        echo "Extract and delete $git_folder_path/archive.tar.gz"
        tar -xzf "$git_folder_path/archive.tar.gz" --strip-components=1 -C "$git_folder_path"
        rm "$git_folder_path/archive.tar.gz"
    done
}

pull-git-folder() {
    local git_folders=$(ls-git-folder)
    for git_folder in $git_folders; do
        echo "Pull git folder $git_folder."
        git -C "$git_folder" stash
        local update=$(git config submodule."$git_folder".update)
        if [[ -z "$update" ]]; then
            git -C "$git_folder" pull origin "$ODOO_VERSION"
        fi;
    done
}

reset-git-folder() {
    local git_folders=$(ls-git-folder)
    for git_folder in $git_folders; do
        echo "Abort rebase and reset git folder $git_folder."
        git -C "$git_folder" rebase --abort
        git -C "$git_folder" reset --hard
    done
}

clean-git-folder() {
    local git_folders=$(ls-git-folder)
    for git_folder in $git_folders; do
        echo "Clean git folder $git_folder."
        git -C "$git_folder" clean -df
    done
}

checkout-git-folder() {
    echo 'Checkout submodule commits.'
    local git_folders=$(ls-git-folder)
    for git_folder in $git_folders; do

        local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow)
        local commit_ref=$(grep "$git_folder" "versions/$ODOO_VERSION/.gitmodules.csv" | cut -d',' -f2)

        if [[ "$shallow" = "true" ]]; then
            echo "Checkout git folder $git_folder (shallow) to $commit_ref"
            git -C "$git_folder" stash
            git -C "$git_folder" fetch origin "$ODOO_VERSION:$ODOO_VERSION"
            git -C "$git_folder" checkout "$commit_ref"
        else
            echo "Checkout git folder $git_folder to $commit_ref"
            git -C "$git_folder" stash
            git -C "$git_folder" checkout "$commit_ref"
        fi
    done
}

switch-git-folder() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    local git_folders=$(ls-git-folder)
    for git_folder in $git_folders; do

        local shallow=$(git config --file .gitmodules --get submodule."$git_folder".shallow)

        if [[ "$shallow" = "true" ]]; then
            echo "Fetch branch $ODOO_VERSION (shallow) from origin and then switch."
            git -C "$git_folder" fetch origin "$ODOO_VERSION:$ODOO_VERSION"
            git -C "$git_folder" switch "$ODOO_VERSION"
        else
            echo "Switch git folder branch to $ODOO_VERSION: $git_folder"
            git -C "$git_folder" stash
            git -C "$git_folder" switch "$ODOO_VERSION"
        fi
    done
}

status-git-folder() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
    local git_folders=$(ls-git-folder "$1")

    echo 'Show status in git folders.'
    for git_folder in $git_folders; do
        git -C "$git_folder" status
    done
}

stage-git-folder() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
    local git_folders=$(ls-git-folder "$1")

    for git_folder in $git_folders; do
        echo "Stage all files in $git_folder."
        git -C "$git_folder" add --all
    done
}

commit-git-folder() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
    if [[ -z "$2" ]]; then echo "\$2 is empty."; exit; fi
    local git_folders=$(ls-git-folder "$1")

    for git_folder in $git_folders; do
        echo "Commit in $git_folder."
        git -C "$git_folder" commit --message "$2"
    done
}

push-git-folder() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
    local git_folders=$(ls-git-folder "$1")

    for git_folder in $git_folders; do
        echo "Push in $git_folder."
        git -C "$git_folder" push
    done
}

sync-git-folder() {
    echo 'Switch and pull submodules.'
    switch-git-folder
    pull-git-folder
}

create-git-feature-branch(){
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi

    local module_name=$(basename "$1")
    local module_path=$(dirname "$1")

    echo "Create feature branch for $module_name in ${module_path}..."
    cd ./"$module_path" || exit
    pwd
    git switch -c "feature-$module_name"
}

create-git-mig-branch(){
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi

    local module_name=$(basename "$1")
    local module_path=$(dirname "$1")

    echo "Create mig branch for $module_name in ${module_path}..."
    cd ./"$module_path" || exit
    pwd
    git switch -c "$ODOO_VERSION-mig-$module_name"
}

# Project commands

load-version() {
    if [[ -n "$1" ]]; then
        ODOO_VERSION="$1"
    fi

    echo "Copy .gitmodules and .python-version from 'versions/$ODOO_VERSION'."
    cp "versions/$ODOO_VERSION/.gitmodules" .
    cp "versions/$ODOO_VERSION/.python-version" .
}

checkout() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi
    ODOO_VERSION="$1"

    # Load .gitmodules and .python-version
    load-version "$ODOO_VERSION"
    # Set .env
    load-revision "$ODOO_VERSION"
    # Clone the addons
    clone-git-folder
    # Checkout commit ref
    checkout-git-folder
    # Upate branch
    switch-git-folder
}

save-version() {
    local git_folders_file="versions/$ODOO_VERSION/.gitmodules.csv"
    echo "Save submodule refs to $git_folders_file."

    local git_folders=$(ls-git-folder)
    echo "path,rev" > "$git_folders_file"
    for git_folder in $git_folders; do
        local commit_ref=$(git -C "$git_folder" rev-parse HEAD)
        echo "$git_folder,$commit_ref" >> "$git_folders_file"
    done

    echo "Copy .gitmodules and .python-version to 'versions/$ODOO_VERSION'"
    cp ".gitmodules" "versions/$ODOO_VERSION/"
    cp ".python-version" "versions/$ODOO_VERSION/"
}


list-versions() {
    ls -1 versions
}

check-version() {
    echo 'Check if Odoo version matches the release file:'
    local odoo_version=$(echo "$ODOO_VERSION" | cut -d'.' -f1)
    grep -E "version_info.+$odoo_version" odoo/odoo/release.py
}

# Revision commands

list-revision() {
    ls -1 revisions
}

show-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo "Odoo Revision: $ODOO_REVISION"
    source "revisions/$ODOO_REVISION"

    echo "Odoo Ref: $ODOO_REF"
    echo "Odoo Enterprise Ref: $ODOO_ENTERPRISE_REF"
    echo "Odoo Themes Ref: $ODOO_THEME_REF"
}

load-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo 'Update revision var in .env file.'
    if grep -q "ODOO_REVISION" .env; then
        sed -i -e "s/^ODOO_REVISION=.*/ODOO_REVISION=${ODOO_REVISION}/" .env
    else
        echo "ODOO_REVISION=${ODOO_REVISION}" >> .env
    fi

    echo "Load refs for revision: $ODOO_REVISION"
    export $(cat "revisions/$ODOO_REVISION" | sed 's/#.*//g' | xargs)
}

checkout-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    load-revision "$ODOO_REVISION"

    echo 'Checkout Odoo git repos...'
    git -C odoo checkout $ODOO_REF
    git -C enterprise checkout $ODOO_ENTERPRISE_REF
    git -C themes checkout $ODOO_THEME_REF
}

create-revision() {
    if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi

    local revision="$1"
    local odoo_date=$(echo "$revision" | cut -d'.' -f3-)
    local before_date=$(date -d "$odoo_date -1 day" +"%Y-%m-%d")
    echo "Checkout Odoo repos on $before_date."

    # Get git commit by date
    cd odoo
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$ref"
    cd ..

    cd enterprise
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local enterprise_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$enterprise_ref"
    cd ..

    cd themes
    git fetch
    echo -e "\nShow last 3 commits from $before_date:"
    git --no-pager log -n 3 --pretty=format:"%h %ad %s" --date=short --before="$before_date" origin/$ODOO_VERSION
    local theme_ref=$(git rev-list -n 1 --before="$before_date" origin/$ODOO_VERSION)
    git checkout "$theme_ref"
    cd ..

    echo -e "\nCreate revision: $revision"
    export ODOO_REVISION=$revision
    export ODOO_REF=$ref
    export ODOO_ENTERPRISE_REF=$enterprise_ref
    export ODOO_THEME_REF=$theme_ref
    envsubst < templates/odoo_revision > "revisions/$revision"
    chmod +x "revisions/$revision"

    echo "Checkout revision: $revision"
    checkout-revision "$revision"
}

commit-and-push-revision() {
    if [[ -n "$1" ]]; then
        ODOO_REVISION="$1"
    fi

    echo 'Remove existing tag...'
    git tag -d "$ODOO_REVISION"

    echo "Commit with revision $ODOO_REVISION tag..."
    git commit --all --message "feat(revision): release $ODOO_REVISION"
    git tag "$ODOO_REVISION"

    echo 'Push tags...'
    git push
    git push -f --tags
}

# RPC commands

test-xmlrpc() {
    load-env "$1"
    echo "Connect to database and count res.partner records: $(images/odoocli/bin/odoocli --model 'res.partner' --method 'search_count' --domain '[]')"
}

odoocli(){
    images/odoocli/bin/odoocli "$@"
}

export-website-data() {
    if [[ -n "$1" ]]; then
        load-env $1
    fi

    export ODOO_URL
    export ODOO_DATABASE
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

    echo -e "\nPython Output:\n\n---"
    python "bin/export-website-data"
    local exit_code=$?
    echo -e "---\n"
    echo -e "Exit code: $exit_code\n"
}

import-website-data() {
    if [[ -n "$1" ]]; then
        load-env $1
    fi

    export ODOO_URL
    export ODOO_DATABASE
    export ODOO_USERNAME
    export ODOO_PASSWORD
    export ODOO_WEBSITE_ID

    echo -e "\nPython Output:\n\n---"
    python "bin/import-website-data"
    local exit_code=$?
    echo -e "---\n"
    echo -e "Exit code: $exit_code\n"
}

# Vuepress commands

install-vuepress() {
    echo 'Install Node.'
    pnpm env use --global lts

    echo 'Install npm packages.'
    pnpm install
}

dev-vuepress() {
    pnpm run dev
}

build-vuepress() {
    pnpm run build
}

serve-vuepress() {
    cd .vuepress/dist
    pnpx serve
}

if declare -f "$1" > /dev/null; then
    activate-venv
    "$1" "${@:2}"
else
    case "$1" in
        source)
            activate-venv
            ;;
        *)
            echo "Unknown command: $1"
            help
            exit 1
            ;;
    esac
fi
