text: from typing import Any, List, Mapping
  
  from .markdown import md, md_line
  from .client import Client
  
  
  class NotionParams:
      """
      Notion API reference https://developers.notion.com/reference/intro
      Find emoji https://emojipedia.org/people/
  
      ```
      from notion_params import NotionParams as NP
      notion = NP.get_client(token)  # or set token in env NOTION_TOKEN
      page_id = '7458781ba20644e0b85045209554ff3d'
      page = notion.retrieve_page(page_id=page_id)
  
      # create sub page, with markdown text
      sub_page = notion.create_page(**NP.create_page(
          page['id'], title='...', emoji='😆',
          text="markdown content", 
      )
      ```
      """
  
      md = md
      md_line = md_line
  
      @staticmethod
      def get_client(token=None):
          return Client(token=token)
  
      @staticmethod
      def create_page(parent_page_id: str, *, title: str, text: str = None, emoji: str = None):
          """
          ```
          sub_page = notion.pages.create(
              **NotionParams.create_page(
                  page['id'],
                  title='title string',
                  emoji='👷',
                  text="markdown...this will be the content",
              )
          )
          ```
          """
          params = {
              # parent https://developers.notion.com/reference/page#page-parent
              'parent': {
                  'type': 'page_id',
                  'page_id': parent_page_id,
              },
              'properties': {
                  # properties https://developers.notion.com/reference/page#all-pages
                  # when parent.type is "page_id", only valid is title
                  # title can have markdown but displayed as normal text
                  # here use md_line only to format notion text param
                  'title': md_line(title),
              },
          }
          if text:
              data = md(text)
              # print('markdown', json.dumps(data, indent=4))
              params['children'] = data
          if emoji:
              params['icon'] = {'emoji': emoji}
          return params
  
      @staticmethod
      def update_page(*, title: str, emoji: str = None, archived=None):
          """
          ```
          notion.pages.update(
              sub_page['id'],
              **NotionParams.update_page(
                  title='new title text',
                  emoji='😃',
              )
          )
          ```
          """
          params = {
              'properties': {
                  'title': md_line(title),
              },
          }
          if emoji:
              params['icon'] = {'emoji': emoji}
          if archived in (True, False):
              params['archived'] = archived
          return params
  
      @staticmethod
      def append_markdown(text: str):
          """options in https://developers.notion.com/reference/patch-block-children
          Usage: `client.append_block_children(block_id, **NP.append_markdown('markdown text'))`
          """
          return {
              'children': md(text),
          }
  
      @staticmethod
      def table_df(df, include_rows=0, has_row_header=True, has_column_header=True):
          """convert DataFrame to one table block
          :param include_rows: number of rows to include, default only include header
              too many rows may cause result too large
          ```
          result = notion.blocks.children.append(
              block_id=sub_page['id'],
              children=[NotionParams.table_df(df)],
          )
          # table = next((i for i in result['results'] if i['type'] == 'table'))  # assume only one table is created
          table = result['results'][0]  # or simpler because we know only one block is created
          for i in range(0, len(df), 100):
              rows = notion.blocks.children.append(
                  block_id=table['id'],
                  children=df[i:i+100]
              )
          ```
          """
          # https://developers.notion.com/reference/block#table-blocks
          header_row = [
              {
                  "type": "table_row",
                  "table_row": {
                      "cells": [
                          [{
                              "text": {"content": col}
                          }]
                          for col in df.columns
                      ]
                  }
              }
          ] if has_column_header else []
          return {
              'type': 'table',
              'table': {
                  "table_width": len(df.columns),
                  "has_column_header": has_column_header,
                  "has_row_header": has_row_header,
                  "children": header_row + NotionParams.table_df_rows(df[:include_rows])
              }
          }
  
      @staticmethod
      def table_df_rows(df):
          """convert all rows to list of blocks, pass in slice if DataFrame too large
          sample
          ```
          notion.blocks.children.append(
              block_id=table_block_id,
              children=NotionParams.table_df_rows(df[100:200]),
          )
          ```
          """
          return df.apply(
              lambda row: {
                  "type": "table_row",
                  "table_row": {
                      "cells": [
                          [{
                              "text": {"content": str(row[col])}
                          }]
                          for col in df.columns
                      ]
                  }
              } if len(row) else {},
              # NOTE: if df length is 0, lambda is invoked once with empty row
              # in this case if reference row[col] will cause confusing error
              # 'DataFrame' object has no attribute 'tolist'
              axis=1,
          ).tolist()
  
      @staticmethod
      def df_columns_add_prefix_for_database(df):
          width = (len(df.columns)-1)//10 + 1  # 2 cols -> 1 digits, 10 cols -> 1 digits, 11 cols -> 2 digits
          return df.rename(columns={
              col: f'{str(i).zfill(width)}-{col}'
              for i, col in enumerate(df.columns[1:])
          })
  
      @staticmethod
      def create_database(page_id, *, title: str, columns: List[str], column_types: Mapping[str, str] = None, emoji: str = None):
          # https://developers.notion.com/reference/create-a-database
          """
          :param column_types: custom column type for each column, if not defined, default is 'rich_text'
  
          demo:
          ```
          df = pd.DataFrame(...)
          db = notion.databases.create(
              **NotionParams.create_database(
                  page['id'],
                  title=db_name,
                  columns=df.columns,
                  emoji='😀',
              ),
          )
          print('created db', json.dumps(db, indent=4))
          for _idx, row in db.iterrows():
              notion.pages.create(**NotionParams.create_database_row(db['id'], row=row))
          # or
          # df.apply(
          #     lambda row: notion.pages.create(
          #         **NotionParams.create_database_row(
          #             db['id'],
          #             row=row,
          #             # emoji='😀',
          #         ),
          #     ),
          #     axis=1,  ### REMEMBER THIS ! to loop lines
          # )
          ```
          """
          column_types = {
              column: (column_types and column_types.get(
                  column) or 'rich_text') if idx else 'title'
              for idx, column in enumerate(columns)
          }
          params = {
              'parent': {
                  'type': 'page_id',
                  'page_id': page_id,
              },
              'title': md_line(title),
              'properties': {
                  column: {column_types[column]: {}}
                  for column in columns
              },
          }
          if emoji:
              params['icon'] = {'emoji': emoji}
          # print(json.dumps(params, indent=4))
          return params
  
      @staticmethod
      def create_database_row(db_id: str, *, row: Mapping[str, Any], columns: List[str] = None, column_types: Mapping[str, str] = None, emoji: str = None):
          """
          ```
          for _idx, row in db.iterrows():
              notion.pages.create(**NotionParams.create_database_row(db['id'], row=row))
          ```
          """
          if columns is None:
              columns = row.keys()
          column_types = {
              column: (column_types and column_types.get(
                  column) or 'rich_text') if idx else 'title'
              for idx, column in enumerate(columns)
          }
          params = {
              'parent': {
                  'type': 'database_id',
                  'database_id': db_id
              },
              'properties': {
                  column: {
                      'type': column_types[column],
                      column_types[column]: row[column] if column_types[column] not in ('title', 'rich_text') else [{
                          # special value format for title, must be array
                          'text': {'content': str(row[column])},
                      }],
                  }
                  for column in columns
              },
              # every database row is a page, here it can have children list of blocks
              # 'children': [{block},...]
          }
          if emoji:
              params['icon'] = {'emoji': emoji}
          # print('create_database_row', json.dumps(params, indent=4))
          return params
  
      @staticmethod
      def find_child(response, *, type_: str, title: str = None):
          """find child in list children response or block append response"""
          if isinstance(response, list):
              # user can pass results array directly, ie NotionParams.find_child(response['results'], ...)
              children = response
          elif isinstance(response, dict):
              # should be normal response
              children = response.get('results') or []
          else:
              raise ValueError('unexpected response type')
          return next((
              i
              for i in children
              if i['type'] == type_
              if title is None or title == i[type_].get('title')
          ), None)
  